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

Как компилятор знает прототип функции сна или даже функцию 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; } 

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

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

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

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

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

 int sleep(); 

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

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

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

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

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

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

 int sleep(int); 

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

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

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

Итак, вкратце, это исторический багаж.

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

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

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

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

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

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