Почему мне приходится повторно объявлять внешнюю функцию?

В настоящее время я читаю «Расширенное программирование в среде Unix» Ричарда Стивенса и написал простую программу, чтобы проверить, как создавать ссылки на файловую систему. После того, как это заработало без проблем, я решил реализовать некоторые опции, начиная с возможности указать -s через командную строку для создания символической ссылки вместо жесткой ссылки. Для этого я набрал link_fn, который имеет значение link, если пользователь не укажет -s, и в этом случае он установлен на symlink.

Проблема в том, что я должен включить extern int symlink(const char* actualpath, const char* sympath);, или я получаю следующую ошибку при запуске make-файла:

link/link.c: In function ‘main’:
link/link.c:30:18: error: ‘symlink’ undeclared (first use in this function)
   30 |             fn = symlink;
      |                  ^~~~~~~
link/link.c:30:18: note: each undeclared identifier is reported only once for each function it appears in
make: *** [Makefile;31: link.o] Error 1

Что странно, я перепроверил как книгу, так и справочные страницы (man 2 symlink), и обе они говорят, что функция symlink объявлена ​​в заголовке unistd.h. Мой раздел заголовка выглядит так:

#if defined(__unix__)
    #include <unistd.h>

    #include <dirent.h>
    #include <fcntl.h>

    #include <sys/mman.h>
    #include <sys/random.h>
    #include <sys/stat.h>
    #include <sys/syscall.h>
    #include <sys/sysctl.h>
    #include <sys/sysinfo.h>
    #include <sys/termios.h>
    #include <sys/types.h>
    #include <sys/user.h>
#elif defined(_WIN32)
    ...
#endif // OS-Dependent modules

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
...

Сначала я убедился, что __unix__ определено, и обе функции symlink и link работают без проблем, но только до тех пор, пока я объявляю symlink внешним, например:

extern int symlink(const char* actualpath, const char* sympath);

Затем я запустил gcc с опцией -E, чтобы убедиться, что unistd.h действительно включается, и, конечно же, он был успешно включен, и функция symlink была тут же:

extern int symlink (const char *__from, const char *__to)
     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2))) ;

Так почему же я получаю ошибку компилятора, когда сам не объявляю прототип функции symlink? И почему функция link не вызывает у меня этой проблемы, когда они объявлены в одном и том же заголовочном файле точно таким же образом?

Вот функция link, также из вывода препроцессора, который я сгенерировал во время отладки.

extern int link (const char *__from, const char *__to)
     __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2))) ;

#if defined(__unix__)
    #include <unistd.h>

    #include <dirent.h>
    #include <fcntl.h>
#elif defined(_WIN32)
    // Removed for brevity
#endif // OS-Dependent modules

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#if !defined(FALSE) || !defined(TRUE)
enum { FALSE, TRUE };
#endif // TRUE || FALSE

// Why does the compiler require this declaration, and only for symlink?
extern int symlink(const char* actualpath, const char* sympath);

typedef int (*link_fn)(const char* path, const char* link_path);

int main(int argc, char *argv[])
{
    if (argc == 2) {
        if (strcmp(argv[1], "--help") == 0) {
            printf("\nUsage: %s <Existing Filename> <New Filename>\n\n", argv[0]);
            printf("Options: \n");
            printf("  -s    Create symbolic link instead of a hard link\n\n");

            return EXIT_SUCCESS;
        }
    }

    if (argc < 3) {
        fprintf(stderr, "Usage: %s <Existing Filename> <New Filename>\n", argv[0]);

        return EXIT_FAILURE;
    }

    link_fn fn = link;

    for (size_t i = 1; i < (size_t) argc - 2; ++i) {
        if (strcmp(argv[i], "-s") == 0) {
            fn = symlink;
        }
    }

    const char* existing_path = argv[argc - 2];
    const char* new_path      = argv[argc - 1];

    errno = 0;

    const int return_code = fn(existing_path, new_path);

    if (return_code == -1) {
        fprintf(stderr, "[Error] %s\n", strerror(errno));

        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

Программа компилируется с использованием:

CC=gcc
CFLAGS = "-std=c17 -Wall -Wextra -Werror -pedantic"

Системная информация

gcc version 9.1.0
Manjaro Linux 18.0.4
x86-64

MRE

Вот минимальный пример, по совету Эрика Постпишила:

#if defined(__unix__)
#include <unistd.h>
#endif

int (*a)(const char*, const char*) = link;
int (*b)(const char*, const char*) = symlink;

int main(void)
{
    //
}

Запуск make дает следующий результат:

gcc -I include -E -o link.pp link/link.c
gcc -I include -S -masm=intel -o link.asm link/link.c
gcc -std=c17 -Wall -Wextra -Werror -pedantic  -I include -c -o link.o link/link.c
link/link.c:9:38: error: ‘symlink’ undeclared here (not in a function)
    9 | int (*b)(const char*, const char*) = symlink;
      |                                      ^~~~~~~
make: *** [Makefile;31: link.o] Error 1

Предварительно обработанный вывод выглядит следующим образом.

 1  # 1 "link/link.c"
 2  # 1 "<built-in>"
 3  # 1 "<command-line>"
 4  # 31 "<command-line>"
 5  # 1 "/usr/include/stdc-predef.h" 1 3 4
 6  # 32 "<command-line>" 2
 7  # 1 "link/link.c"
 ...
12  # 1 "/usr/include/unistd.h" 1 3 4
13  # 25 "/usr/include/unistd.h" 3 4
14  # 1 "/usr/include/features.h" 1 3 4
15  # 450 "/usr/include/features.h" 3 4
16  # 1 "/usr/include/sys/cdefs.h" 1 3 4
17  # 452 "/usr/include/sys/cdefs.h" 3 4
18  # 1 "/usr/include/bits/wordsize.h" 1 3 4
19  # 453 "/usr/include/sys/cdefs.h" 2 3 4
20  # 1 "/usr/include/bits/long-double.h" 1 3 4
21  # 454 "/usr/include/sys/cdefs.h" 2 3 4
22  # 451 "/usr/include/features.h" 2 3 4
23  # 474 "/usr/include/features.h" 3 4
24  # 1 "/usr/include/gnu/stubs.h" 1 3 4
25  # 10 "/usr/include/gnu/stubs.h" 3 4
26  # 1 "/usr/include/gnu/stubs-64.h" 1 3 4
27  # 11 "/usr/include/gnu/stubs.h" 2 3 4
28  # 475 "/usr/include/features.h" 2 3 4
29  # 26 "/usr/include/unistd.h" 2 3 4
... 
32  # 202 "/usr/include/unistd.h" 3 4
33  # 1 "/usr/include/bits/posix_opt.h" 1 3 4
34  # 203 "/usr/include/unistd.h" 2 3 4
...
38  # 1 "/usr/include/bits/environments.h" 1 3 4
39  # 22 "/usr/include/bits/environments.h" 3 4
40  # 1 "/usr/include/bits/wordsize.h" 1 3 4
41  # 23 "/usr/include/bits/environments.h" 2 3 4
42  # 207 "/usr/include/unistd.h" 2 3 4
43  # 217 "/usr/include/unistd.h" 3 4
44  # 1 "/usr/include/bits/types.h" 1 3 4
45  # 27 "/usr/include/bits/types.h" 3 4
46  # 1 "/usr/include/bits/wordsize.h" 1 3 4
47  # 28 "/usr/include/bits/types.h" 2 3 4
48  # 1 "/usr/include/bits/timesize.h" 1 3 4
49  # 29 "/usr/include/bits/types.h" 2 3 4
...
53  # 31 "/usr/include/bits/types.h" 3 4
54  typedef unsigned char __u_char;

Объявления других функций и перечисления объявлены примерно на тысячу строк. Затем в строке 1169:

  1169  extern int link (const char *__from, const char *__to)
  1170       __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2))) ;
  1171  
  1172  
  1173  
  1174  
  1175  extern int linkat (int __fromfd, const char *__from, int __tofd,
  1176       const char *__to, int __flags)
  1177       __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (2, 4))) ;
  1178  
  1179  
  1180  
  1181  
  1182  extern int symlink (const char *__from, const char *__to)
  1183       __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1, 2))) ;

И пропуская несколько сотен строк подобных заявлений:

  1416  
  1417  # 6 "link/link.c" 2
  1418  
  1419  
  1420  
  1421  # 8 "link/link.c"
  1422  int (*a)(const char*, const char*) = link;
  1423  int (*b)(const char*, const char*) = symlink;
  1424  
  1425  int main(void)
  1426  {
  1427  
  1428  }

Я использовал gcc -dM -E - < /dev/null | grep -E 'unix', который показал, что __unix__, __unix и unix все определены со значением 1. Затем я использовал старый добрый оператор печати #if defined(__unix__), чтобы убедиться, что он действительно сработал.

Jose Fernando Lopez Fernandez 29.05.2019 04:20

Вы должны сократить это до минимальный воспроизводимый пример: напишите просто #if defined __unix__ / #include <unistd.h> / #endif / int (*a)(const char *, const char *) = link; / int (*b)(const char *, const char *) = symlink; и покажите результат компиляции (генерируемые сообщения об ошибках или их отсутствие), а затем покажите результат компиляции с -E.

Eric Postpischil 29.05.2019 04:32

Я немного удивлен, что компиляция в сборочный файл работает нормально (кстати, зачем вы это делаете?), но компиляция в объектный файл не работает. Что произойдет, если вы включите одни и те же флаги компилятора для всех сборок? Изменяет ли он предварительно обработанный вывод? Приводит ли это к ошибкам при создании файла сборки?

Some programmer dude 29.05.2019 05:04

В свете ответ от ричи я бы предположил, что соответствующий флаг — это опция -std=c17, которая заставляет GCC не включать специфичные для платформы определения, необходимые для объявляемых функций. Если вы это сделаете gcc -I include -E -std=c17 -o link.pp link/link.c (обратите внимание на добавленный флаг), действительно ли функции объявлены в сгенерированном link.pp файле? Урок здесь заключается в том, чтобы всегда использовать одни и те же параметры и флаги компилятора для команд сборки все.

Some programmer dude 29.05.2019 05:09

Вау, это сработало. Я хотел посмотреть, как далеко я смогу продвинуться в процессе сборки, но совершенно забыл использовать те же самые флаги. Попытка получить вывод ассемблера с этой переменной CFLAGS также не удалась, и удаление объявления std, как вы упомянули, заставило все скомпилироваться без проблем. Я попробовал c99 и c11 без других настроек, и они тоже вызвали ошибку.

Jose Fernando Lopez Fernandez 29.05.2019 05:11
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
5
128
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Чтобы symlink было объявлено, вам нужно #define правильно указать макрос функционального тестирования, как указано в symlink справочная страница:

Feature Test Macro Requirements for glibc (see feature_test_macros(7)):

       symlink():
           _XOPEN_SOURCE >= 500 || _POSIX_C_SOURCE >= 200112L
               || /* Glibc versions <= 2.19: */ _BSD_SOURCE

Вы должны выработать привычку использовать макросы для тестирования функций, хотя заголовки Gnu обычно позволяют вам опускать их, если вы используете параметр -std по умолчанию или любой параметр -std, начинающийся с gnu. gnu «стандарты» определяют _GNU_SOURCE макрос проверки функций, который эффективно обходит все проверки функций. Некоторым это может показаться удобным, но это приводит к раздражающим проблемам с переносимостью.

Хотя технически лучше помещать определение вверху каждого исходного файла, я считаю более удобным помещать -D_XOPEN_SOURCE=700 в свой CFLAGS, что также гарантирует, что определение идет перед любым #include.

Кроме того, имейте в виду, что если вы хотите точно выполнить предварительную обработку исходного кода, вам необходимо убедиться, что ваши CFLAGS для команды предварительной обработки такие же, как и для вашей компиляции. В противном случае вы можете пропустить тот факт, что некоторые символы не определены (или предопределены) с более строгими параметрами -std.

Это довольно смущает, я проконсультировался со страницей руководства, но я не прочитал достаточно глубоко. Большое спасибо, я не слишком много использовал макросы для тестирования функций, но это определенно урок, который я не скоро забуду.

Jose Fernando Lopez Fernandez 29.05.2019 05:15

Другие вопросы по теме