Почему мне нужно переопределить внешнюю функцию? - PullRequest
2 голосов

В настоящее время я читаю Расширенное программирование в среде Unix Ричарда Стивенса, и я написал базовую программу, чтобы проверить, как создавать ссылки на файловую систему. После того, как это сработало без проблем, я решил реализовать некоторые опции, начиная с возможности указать -s через командную строку для создания символической ссылки вместо жесткой ссылки. Чтобы сделать это, я ввожу команду defdef'd 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  }

1 Ответ

3 голосов
/ 29 мая 2019

Чтобы объявить symlink, необходимо #define правильный макрос проверки характеристик , как указано в symlink manpage :

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 параметрами.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...