Является ли объявление заголовочного файла необходимым? - PullRequest
4 голосов
/ 14 декабря 2010

Является ли объявление файла заголовка необходимым? Этот код:

main()
{
  int i=100;

  printf("%d\n",i);
}

, кажется, работает, вывод, который я получаю, равен 100. Даже без использования заголовочного файла stdio.h. Как это возможно?

Ответы [ 5 ]

14 голосов
/ 14 декабря 2010

У вас нет для включения файла заголовка.Его цель - дать компилятору знать всю информацию о stdio, но это не обязательно, если ваш компилятор умный (или ленивый).

Вы должны включить его, потому что этохорошая привычка - если вы этого не сделаете, то у компилятора нет реального способа узнать, нарушаете ли вы правила, например:

int main (void) {
    puts (7);       // should be a string.
    return 0;
}

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

#include <stdio.h>
int main (void) {
    puts (7);
    return 0;
}

приведет к тому, что компилятор предупредит вас примерно так:

qq.c:3: warning: passing argument 1 of ‘puts’ makes pointer
                 from integer without a cast

Приличный компилятор может предупредить вас об этом, например, gcc, зная о том, чтоprintf должен выглядеть, даже без заголовка:

qq.c:7: warning: incompatible implicit declaration of
                 built-in function ‘printf’
9 голосов
/ 14 декабря 2010

Как это возможно?Короче говоря: три удачи.

Это возможно, потому что некоторые компиляторы делают предположения о необъявленных функциях.В частности, параметры предполагаются равными int, а тип возвращаемого значения также int.Поскольку int часто имеет размер, равный char* (в зависимости от архитектуры), вы можете избежать передачи int s и строк, так как правильный параметр размера будет помещен в стек.

В вашем примере, поскольку printf не был объявлен, предполагалось, что он принимает два параметра int, а вы передали char* и int, которые "совместимы" с точки зрения вызова.Поэтому компилятор пожал плечами и сгенерировал некоторый код, который должен был быть примерно правильным.(Он действительно должен был предупредить вас о необъявленной функции.)

Итак, первая удача заключалась в том, что допущение компилятора было совместимо с реальной функцией.

Затем на этапе компоновщика, потому чтоprintf является частью стандартной библиотеки C, компилятор / компоновщик автоматически включит ее на этапе компоновки.Поскольку символ printf действительно был в C-stdlib, компоновщик разрешил этот символ, и все было хорошо.Связывание было второй удачей, так как для функции, отличной от стандартной библиотеки, потребуется также ее библиотека.

Наконец, во время выполнения мы видим вашу третью часть удачи.Компилятор сделал слепое предположение, символ оказался связанным по умолчанию.Но - во время выполнения вы могли бы легко передать данные таким образом, чтобы вывести из строя ваше приложение.К счастью, параметры совпали, и в итоге все получилось.Это, конечно, не всегда будет так, и я полагаю, что вышеперечисленное могло бы произойти сбой в 64-битной системе.

Итак, чтобы ответить на исходный вопрос, действительно важно включить заголовочные файлы, потому что еслиэто работает, это только благодаря слепой удаче!

0 голосов
/ 14 декабря 2010

C поддерживает три типа форм аргументов функции:

  1. Известные фиксированные аргументы: это когда вы объявляете функцию с аргументами: foo(int x, double y).
  2. Неизвестные фиксированные аргументы: этокогда вы объявляете это с пустыми скобками: foo() (не путайте с foo(void): это первая форма без аргументов), или не объявляйте это вообще.
  3. Переменные аргументы: это когда вы объявляете это с помощью многоточия: foo(int x, ...).

Когда вы видите, как работает стандартная функция, то определение функции (в форме 1 или 3) совместимо сФорма 2 (с использованием того же соглашения о вызовах).Многие старые станд.библиотечные функции таковы (как предполагалось), потому что они существуют в ранних версиях C, где не было объявлений функций, и все они были в форме 2. Другая функция может быть непреднамеренно несовместимой с формой 2, если они имеют аргументы какзаявленные в аргументе правила продвижения по этой форме.Но некоторые могут не быть такими.

Но для формы 2 требуется, чтобы программист везде передавал аргументы одинаковых типов, потому что компилятор не может проверять аргументы с прототипом и должен определять соглашение о вызовах, исключая фактически переданные аргументы.

Например, на компьютере MC68000 первые два целочисленных аргумента для фиксированных функций arg (для обеих форм 1 и 2) будут переданы в регистрах D0 и D1, первые два указателя в A0 и A1, вседругие прошли через стек.Так, например, функция fwrite(const void * ptr, size_t size, size_t count, FILE * stream); получит аргументы в виде: ptr в A0, size в D0, count в D1 и stream в A1 (и вернет результат вD0).Когда вы включите stdio.h, это будет так, что бы вы ни передали ему.

Если вы не включите stdio.h, произойдет другая вещь.Когда вы вызываете fwrite с fwrite(data, sizeof(*data), 5, myfile), компилятор просматривает аргументы и видит, что функция называется fwrite(*, int, int, *).Так, что это делает?Он передает первый указатель в A0, первый int в D0, второй int в D1 и второй указатель в A1, так что это то, что нам нужно.

Но когда вы пытаетесь вызвать его какfwrite(data, sizeof(*data), 5.0, myfile), с count типа double, компилятор попытается пропустить count через стек, так как он не является целым числом.Но функция require находится в D1.Дерьмо случается: D1 содержит немного мусора, а не count, поэтому дальнейшее поведение непредсказуемо.Но чем вы используете прототип, определенный в stdio.h, все будет в порядке: компилятор автоматически преобразует этот аргумент в int и передает его по мере необходимости.Это не абстрактный пример, поскольку double в arument может быть просто результатом вычислений с числами с плавающей запятой, и вы можете просто пропустить этот допущенный результат: int.

Другой пример - функция переменного аргумента (форма 3), такая как printf(char *fmt, ...),Для этого соглашение о вызовах требует, чтобы последний именованный аргумент (fmt здесь) был пропущен через стековый тип этого типаИтак, затем вы вызываете printf("%d", 10), он поместит указатель на "%d" и номер 10 в стек и вызовет функцию по мере необходимости.

Но когда вы не включите stdio.h, компилятор не будет знать, что printf является функцией vararg и предполагает, что printf("%d", 10) вызывает функцию с fixed аргументами типа pointer и int.Таким образом, MC68000 поместит указатель на A0 и int на D0 вместо стека, и результат снова будет непредсказуемым.

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

Итак: не делайте этого, даже если вам повезло, и это работает.Неизвестная форма с фиксированным аргументом существует только для совместимости со старым кодом и строго не рекомендуется использовать.

Также обратите внимание: C++ не позволит этого вообще, так как требует, чтобы функция былаобъявлено с известными аргументами.

0 голосов
/ 14 декабря 2010

Это возможно, потому что, когда компилятор C видит необъявленный вызов функции (printf () в вашем случае), он предполагает, что он имеет

<code>
int printf(...)

подпись и пытаетсяназовите это приведением всех аргументов к типу int.Поскольку типы "int" и "void *" часто имеют одинаковый размер, он работает большую часть времени.Но не стоит полагаться на такое поведение.

0 голосов
/ 14 декабря 2010

Как сказал paxidiablo, в этом нет необходимости, но это верно только для функций и переменных, но если в вашем заголовочном файле есть некоторые типы или макросы (#define), которые вы используете, вы должны включить заголовочный файл, чтобы использовать их, потому что они нужны до связывание происходит, т.е. во время предварительной обработки или компиляции

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