Как компилятор знает прототип функции сна или даже функцию 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
. Существуют и другие варианты, которые могут быть полезны, если вы делаете что-то вроде создания ядра доморощенного или какого-либо другого приложения на металле.
В примере, отличном от игрушки, другой файл может содержать тот, который вы пропустили. Просмотр вывода из препроцессора – хороший способ увидеть, что вы в конечном итоге скомпилируете.