Оставляя передовые декларации (прототипы)
Я учу 14 на знаменитом онлайн-курсе "Learn C The Hard Way".
В этом уроке он вводит понятие форвардных объявлений в C
.
В образце кода есть две форвардные объявления. Один из них можно прокомментировать, и код все еще компилируется, но другой нельзя прокомментировать. Для меня они оба выглядят одинаково важными.
Вот код. Он просто распечатывает все символы и их шестнадцатеричные коды, если они принадлежат алфавиту, в противном случае он пропускает их.
Два выхода компилятора находятся в нижней части кода.
Может кто-нибудь объяснить, почему одна ошибка, а другая нет?
#include <stdio.h>
#include <ctype.h>
// forward declarations
int can_print_it(char ch); //NOT OK to skip(??)
void print_letters(char arg[]); //OK to skip(??)
void print_arguments(int argc, char *argv[])
{
int i = 0;
for(i = 0; i < argc; i++) {
print_letters(argv[i]);
}
}
void print_letters(char arg[])
{
int i = 0;
for(i = 0; arg[i] != '\0'; i++) {
char ch = arg[i];
if(can_print_it(ch)) {
printf("'%c' == %d ", ch, ch);
}
}
printf("\n");
}
int can_print_it(char ch)
{
return isalpha(ch) || isblank(ch);
}
int main(int argc, char *argv[])
{
print_arguments(argc, argv);
return 0;
}
Если я прокомментирую первое прямое объявление (только первое), это произойдет:
cc -Wall -g ex14.c -o ex14
ex14.c: In function ‘print_letters’:
ex14.c:24:9: warning: implicit declaration of function ‘can_print_it’ [-Wimplicit-function-declaration]
ex14.c: At top level:
ex14.c:32:5: error: conflicting types for ‘can_print_it’
ex14.c:33:1: note: an argument type that has a default promotion can’t match an empty parameter name list declaration
ex14.c:24:12: note: previous implicit declaration of ‘can_print_it’ was here
make[1]: *** [ex14] Error 1
make[1]: Leaving directory `/home/andrew/c_tutorials/lesson14/ex14_original'
make: *** [all] Error 2
И если я прокомментирую второе объявление (только второе), это произойдет:
cc -Wall -g ex14.c -o ex14
ex14.c: In function ‘print_arguments’:
ex14.c:13:9: warning: implicit declaration of function ‘print_letters’ [-Wimplicit-function-declaration]
ex14.c: At top level:
ex14.c:17:6: warning: conflicting types for ‘print_letters’ [enabled by default]
ex14.c:13:9: note: previous implicit declaration of ‘print_letters’ was here
make[1]: Leaving directory `/home/andrew/c_tutorials/lesson14/ex14_original'
Ответы
Ответ 1
Хорошо компилятор подсказывает, почему это происходит. Самое главное здесь:
ex14.c:32:5: error: conflicting types for ‘can_print_it’
ex14.c:33:1: note: an argument type that has a default promotion can’t match an empty parameter name list declaration
Аргумент для can_print_it
имеет поощрение по умолчанию, поэтому он не может иметь неявное объявление. Большое внимание на нем можно найти здесь: Активные объявления по умолчанию в вызовах функций C. В принципе, тип аргумента для can_print_it
(char
) является незаконным для использования с неявными объявлениями. Чтобы он работал, вам нужно будет использовать соответствующий тип, для char
это int
. Для других типов вы можете проверить связанный вопрос и ответить.
print_letters
не имеет таких аргументов, его аргумент имеет тип указателя.
Боковое примечание. Как видно, с 3 неправильными ответами люди путаются. Неявные декларации часто не используются и могут быть сложными. ИМО в целом или, по крайней мере, в практических приложениях, их использование не рекомендуется. Тем не менее, они совершенно законны.
Ответ 2
Вы даете прототип функции, чтобы компилятор знал, что делать, когда он впервые встречает эту функцию в вашем коде. В частности, если у него нет другой информации, компилятор будет
- предположим, что возвращаемое значение равно
int
- продвигать аргументы:
- "целочисленные типы" до
int
(поэтому char
становится int
, например)
- продвигать
float
в double
- указатели становятся указателями на
int
Проблема заключается в том, что при преобразовании char
в int
возможно, что старший байт заканчивается смещением (например) 3 байтами от того места, где, по вашему мнению, вы его сохранили, - поскольку значение типа 0x33
может храниться как 0x00000033
. В зависимости от архитектуры машины это вызовет проблему.
То же самое не относится к указателям. Указатель "всегда" имеет одинаковый размер и всегда указывает на первый байт объекта (это не всегда было правдой... некоторые из нас помнят "ближние" и "дальние" указатели, а не ностальгию). Таким образом, несмотря на то, что компилятор может считать, что он передает указатель на int
, последующая интерпретация (по функции, которая была не указана) как указатель на char
, не вызывает проблемы.
Тот факт, что ваша вторая функция объявлена как void
, когда компилятор предположил, что он вернет int
, не имеет значения, так как вы никогда не использовали его возвращаемое значение (которого у него нет) в присваивании или выражении. Поэтому, хотя это немного запутывает компилятор, это порождает только предупреждение, а не ошибку. И поскольку аргумент является указателем, правила продвижения снова не вызывают конфликта.
Тем не менее, это хорошая идея всегда объявлять прототипы функций перед их использованием; в общем, вы должны включить все предупреждения компилятора и улучшить свой код, пока он не будет компилироваться без предупреждений или ошибок.