В настоящее время я читаю «Расширенное программирование в среде 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
Вот минимальный пример, по совету Эрика Постпишила:
#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 }
Вы должны сократить это до минимальный воспроизводимый пример: напишите просто #if defined __unix__
/ #include <unistd.h>
/ #endif
/ int (*a)(const char *, const char *) = link;
/ int (*b)(const char *, const char *) = symlink;
и покажите результат компиляции (генерируемые сообщения об ошибках или их отсутствие), а затем покажите результат компиляции с -E
.
Я немного удивлен, что компиляция в сборочный файл работает нормально (кстати, зачем вы это делаете?), но компиляция в объектный файл не работает. Что произойдет, если вы включите одни и те же флаги компилятора для всех сборок? Изменяет ли он предварительно обработанный вывод? Приводит ли это к ошибкам при создании файла сборки?
В свете ответ от ричи я бы предположил, что соответствующий флаг — это опция -std=c17
, которая заставляет GCC не включать специфичные для платформы определения, необходимые для объявляемых функций. Если вы это сделаете gcc -I include -E -std=c17 -o link.pp link/link.c
(обратите внимание на добавленный флаг), действительно ли функции объявлены в сгенерированном link.pp
файле? Урок здесь заключается в том, чтобы всегда использовать одни и те же параметры и флаги компилятора для команд сборки все.
Вау, это сработало. Я хотел посмотреть, как далеко я смогу продвинуться в процессе сборки, но совершенно забыл использовать те же самые флаги. Попытка получить вывод ассемблера с этой переменной CFLAGS
также не удалась, и удаление объявления std
, как вы упомянули, заставило все скомпилироваться без проблем. Я попробовал c99
и c11
без других настроек, и они тоже вызвали ошибку.
Чтобы 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
.
Это довольно смущает, я проконсультировался со страницей руководства, но я не прочитал достаточно глубоко. Большое спасибо, я не слишком много использовал макросы для тестирования функций, но это определенно урок, который я не скоро забуду.
Я использовал
gcc -dM -E - < /dev/null | grep -E 'unix'
, который показал, что__unix__
,__unix
иunix
все определены со значением 1. Затем я использовал старый добрый оператор печати#if defined(__unix__)
, чтобы убедиться, что он действительно сработал.