Почему мы не получаем ошибку времени компиляции, даже если мы не включаем stdio.h в программу на C? - PullRequest
6 голосов
/ 16 сентября 2008

Как компилятор узнает прототип функции сна или даже функции printf, когда я вообще не включил заголовочный файл?

Более того, если я укажу sleep(1,1,"xyz") или любое произвольное количество аргументов, компилятор все равно его скомпилирует. Но странно то, что gcc может найти определение этой функции во время соединения, я не понимаю, как это возможно, потому что фактическая функция sleep() принимает только один аргумент, но наша программа упомянула три аргумента.

/********************************/
int main()
{
 short int i;
 for(i = 0; i<5; i++)
 {
    printf("%d",i);`print("code sample");`
    sleep(1);
 }
 return 0;
}

Ответы [ 7 ]

10 голосов
/ 16 сентября 2008

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

В зависимости от архитектуры процессора аргументы могут передаваться в регистрах (например, от a0 до a3 в MIPS) или помещаться в стек, как в исходном соглашении о вызовах x86. В любом случае передача лишних аргументов безвредна. Вызываемая функция не будет использовать переданные регистры и ссылаться на дополнительные аргументы в стеке, но ничего плохого не происходит.

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

5 голосов
/ 16 сентября 2008

В классическом C вам не нужен прототип для вызова функции. Компилятор определит, что функция возвращает int и принимает неизвестное количество параметров. Это может работать на некоторых архитектурах, но не получится, если функция вернет что-то отличное от int, например структуру, или если есть какие-либо преобразования параметров.

В вашем примере виден сон, а компилятор принимает прототип, подобный

int sleep();

Обратите внимание, что список аргументов пуст. В C это не то же самое, что void. Это на самом деле означает «неизвестно». Если бы вы писали код на языке K & R, вы могли бы иметь неизвестные параметры через код, такой как

int sleep(t)
int t;
{
   /* do something with t */
}

Это все опасно, особенно на некоторых встроенных чипах, где способ передачи параметров для непрототипированной функции отличается от способа с прототипом.

Примечание: прототипы не нужны для связи. Обычно компоновщик автоматически связывается с библиотекой времени выполнения C, например, glibc в Linux. Связь между вашим использованием sleep и кодом, который его реализует, возникает во время соединения еще долго после обработки исходного кода.

Я бы посоветовал вам использовать функцию вашего компилятора, чтобы требовать прототипы, чтобы избежать подобных проблем. С GCC это аргумент командной строки -Wstrict-prototypes. В инструментах CodeWarrior это был флаг «Требовать прототипы» на панели компилятора C / C ++.

2 голосов
/ 16 сентября 2008

Другие ответы охватывают вероятную механику (все предположения в качестве компилятора не указаны).

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

2 голосов
/ 16 сентября 2008

Это связано с тем, что называется «K & R C» и «ANSI C». В старом добром K & R C, если что-то не объявлено, предполагается, что это int. Так что любая вещь, которая выглядит как вызов функции, но не объявлена ​​как функция автоматически примет возвращаемое значение типа int и аргумента в зависимости по актуальному вызову.

Однако позже люди поняли, что иногда это может быть очень плохо. Так несколько компиляторов добавили предупреждение. C ++ сделал эту ошибку. Я думаю, что у GCC есть некоторые флаг (-ansic или -pedantic?), делающий это условие ошибочным.

Итак, в двух словах, это исторический багаж.

2 голосов
/ 16 сентября 2008

C будет угадывать int для неизвестных типов. Таким образом, он, вероятно, думает, что сон имеет этот прототип:

int sleep(int);

Что касается указания нескольких параметров и связывания ... Я не уверен. Это меня удивляет. Если это действительно сработало, то что случилось во время выполнения?

1 голос
/ 16 сентября 2008

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

1 голос
/ 16 сентября 2008

Зависит от компилятора, но с помощью gcc (например, поскольку это тот, на который вы ссылались), некоторые стандартные (как C, так и POSIX) функции имеют встроенные «встроенные функции компилятора». Это означает, что библиотека компилятора, поставляемая с вашим компилятором (в данном случае libgcc), содержит реализацию функции. Компилятор разрешит неявное объявление (то есть, используя функцию без заголовка), и компоновщик найдет реализацию в библиотеке компилятора, потому что вы, вероятно, используете компилятор в качестве внешнего интерфейса компоновщика.

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

В качестве альтернативы, gcc поддерживает опции для отключения использования встроенных функций: -fno-builtin или для гранулярного управления, -fno-builtin-function. Существуют и другие варианты, которые могут быть полезны, если вы делаете что-то вроде сборки ядра для homebrew или какого-либо другого приложения на железной дороге.

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