Подтвердить что ты не робот

Оставляя передовые декларации (прототипы)

Я учу 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'
4b9b3361

Ответ 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, не имеет значения, так как вы никогда не использовали его возвращаемое значение (которого у него нет) в присваивании или выражении. Поэтому, хотя это немного запутывает компилятор, это порождает только предупреждение, а не ошибку. И поскольку аргумент является указателем, правила продвижения снова не вызывают конфликта.

Тем не менее, это хорошая идея всегда объявлять прототипы функций перед их использованием; в общем, вы должны включить все предупреждения компилятора и улучшить свой код, пока он не будет компилироваться без предупреждений или ошибок.