C поддерживает три типа форм аргументов функции:
- Известные фиксированные аргументы: это когда вы объявляете функцию с аргументами:
foo(int x, double y)
. - Неизвестные фиксированные аргументы: этокогда вы объявляете это с пустыми скобками:
foo()
(не путайте с foo(void)
: это первая форма без аргументов), или не объявляйте это вообще. - Переменные аргументы: это когда вы объявляете это с помощью многоточия:
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++
не позволит этого вообще, так как требует, чтобы функция былаобъявлено с известными аргументами.