Подтвердить что ты не робот

C Функция с параметром без индикатора типа по-прежнему работает?

Код выглядит следующим образом:

int func(param111)
{
    printf("%d\n", param111);
    return param111;
}

int main()
{
    int bla0 = func(99);
    int bla1 = func(10,99);
    int bla2 = func(11111110,99,10001);
    printf("%d, %d, %d\n", bla0, bla1, bla2);
}

Результат компиляции:

[email protected]:~$ gcc -Wall -g -std=c99 -O2 zeroparam.c

zeroparam.c: In function ‘func’:

zeroparam.c:2: warning: type of ‘param111’ defaults to ‘int’

Результат выполнения:

[email protected]:~$ ./a.out

99

10

11111110

99, 10, 11111110

Я знаю, что код должен быть в порядке, если func с нулевыми параметрами, например int func(), который будет принимать входы any. Но как этот код скомпилирован и успешно запущен?

4b9b3361

Ответ 1

Это поведение обеспечивает обратную совместимость со старыми версиями языка, версией языка K & R. Когда GCC встречает функцию "старого стиля", она соответствует старому поведению K & R C, которое не подразумевает никаких предупреждений в этой ситуации.

Действительно, если вы измените функцию на: int func(int param111), вы сделаете получите ожидаемые предупреждения:

x.c: In function ‘main’:
x.c:11:5: error: too many arguments to function ‘func’
x.c:2:5: note: declared here
x.c:12:5: error: too many arguments to function ‘func’
x.c:2:5: note: declared here
x.c:14:1: warning: control reaches end of non-void function [-Wreturn-type]

(Протестировано с помощью GCC 4.7.3 и "gcc -std = c99 -Wall x.c &./a.out" )

Или процитировать JeremyP из комментариев: "В K & RC было совершенно нормально вызвать функцию с таким количеством аргументов, как вам нравится, потому что тогда обозначение эллипсиса не было изобретено".

Обратите внимание, что компилятор может показать столько дополнительных предупреждений, которые он хочет, и по-прежнему соответствовать стандарту. Например, компилятор Apple предупреждает об этом коде.

Ответ 2

Объявление функции интерпретируется как объявление функции стиля K & R, поскольку в ней отсутствуют типы. В стандартном случае это называется объявлением функции с списком идентификаторов, в отличие от списка типов параметров, как в обычном объявлении.

В соответствии со спецификацией C99, 6.9.1/7, только функциональные определения с перечнем типов параметров считаются прототипами функций. Вместо этого стиль K & R использует список идентификаторов, и поэтому не считается прототипом.

Вызов функций для функций без прототипов не проверяется на количество или типы параметров (по 6.5.2.2/8), количество и типы аргументов не сравниваются с числом параметров в определении функции, которое не включает функцию прототип декларатора "). Таким образом, законно вызывать функцию, объявленную в стиле K & R, с любым числом и типом аргументов, но в 6.5.2.2/9 вызов с недопустимыми типами приведет к поведению undefined.

В качестве иллюстрации следующее будет компилироваться без каких-либо предупреждений (на gcc -Wall -Wextra -pedantic -std=c99 -O):

#include <stdio.h>

void *func(param111)
char *param111;
{
    printf("%s\n", param111);
    return param111;
}

int main()
{
    void *bla0 = func();
    void *bla1 = func(99);
    void *bla2 = func(11111110,99);
    printf("%p, %p, %p\n", bla0, bla1, bla2);
    return 0;
}

несмотря на то, что, очевидно, имеют неправильные типы и количество параметров.

Ответ 3

Он интерпретируется как K & R C, как объяснили другие. Стоит отметить, что это поведение undefined в ANSI C:

C11 6.9.1 Определения функций Раздел 9

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

Таким образом, функция аргументов с переменными номерами должна заканчиваться на ... как параметр, например printf:

int printf( const char *format ,...);

Ответ 4

Я могу объяснить, почему это работает, но не почему компилятор не предупреждает об этом.

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

Для вашего случая с func (10, 99), "main" выталкивает значения в стек в следующем порядке (справа налево):

99
10

"func" знает только одно значение и берет его с конца, поэтому param111 == 10.

Затем "main", зная, что были перенесены два аргумента, возвращает их назад, тем самым очищая стек.

Ответ 5

Функция func в вашем коде имеет только определение функции, но не декларатор функции. В C99 6.5.2.2 (вызовы функций) он определяет:

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

Когда вызывается func(10,99) и func(11111110, 99, 10001), компилятор не будет сравнивать число и типы аргументов с параметрами в определении функции. Вы можете даже позвонить через func("abc"). Однако, если вы добавите в свой код следующее объявление func:

int func(int);

(int fun(int) объявлен, потому что стандарт C99 будет неявно продвигать тип para111 to int), компилятор отправил бы следующие ошибки:

zeroparam.c: В функции main:
zeroparam.c: 15: 13: ошибка: слишком много аргументов для функции func
zeroparam.c: 6: 5: примечание: объявлено здесь
zeroparam.c: 16: 17: ошибка: слишком много аргументов для функции func

BTW: Я не думаю, что это проблема "K & R program", поскольку вы явно указываете "-std = c99" в своей команде.

Ответ 6

Если вы не получаете никаких предупреждений для этого кода, это потому, что ваш компилятор не применяет правила C99 (вызов printf или любая функция без видимого объявления является нарушением ограничения). Вероятно, вы можете получить хотя бы некоторые предупреждения, передав нужные параметры своему компилятору. Если вы используете gcc, попробуйте gcc -std=c99 -pedantic -Wall -Wextra.

Так называемый K & RC, язык, описанный в 1978 году в 1-м выпуске классической книги Kernighan и Ritchie Язык программирования C, сделал не имеют прототипов функций. (Прототип - это объявление функции, определяющее типы его параметров.) Определение функции все равно должно было определять его параметры (возможно, неявно), но объявление не было, а типичные компиляторы не проверяли правильное сопоставление аргументов (в вызов функции) параметрам (в определении функции).

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

В стандарте ANSI C 1989 года (переизданном как стандарт ISO C 1990 года) были введены прототипы (заимствованные с раннего С++), но не требовали их. Но он явно указал, что вызов функции с неправильным числом или типами аргументов вызывает поведение undefined; компилятор не обязан предупреждать вас об этом, но программа может делать буквально все, когда вы его запускаете.

В стандарте ISO C 1999 года было исключено правило "неявное int" и было запрещено его использование (нарушение ограничений) для вызова функции без видимого объявления, но она все же допускала объявления и определения функций старого стиля. Поэтому в соответствии с правилами K & R1 и C89/C90 определение функции:

int func(param111)
{
    printf("%d\n", param111);
    return param111;
}

и param111 имеет тип int. В соответствии с правилами C99 он недействителен, но это:

int func(param111)
int param111;
{
    printf("%d\n", param111);
    return param111;
}

по-прежнему является законным (и остается законным даже по стандарту 2011 года).

Как и в случае с C99 и C11, если вы вызываете функцию, чье видимое объявление не является прототипом, все зависит от вас, чтобы получить правильные аргументы; компилятор не должен предупреждать вас, если вы ошибаетесь.

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

О, и вам нужно добавить

#include <stdio.h>

в начало исходного файла, так как вы вызываете printf. В правилах C89/C90 вызов printf без видимого объявления имеет поведение undefined (потому что printf принимает переменное количество аргументов). В C99 и более поздних версиях это нарушение ограничений, требующее диагностики времени компиляции.

Я не разбирался в объявлении отсутствующего параметра. Немного измененный вариант вашей программы:

#include <stdio.h> /* add this line */

int func(param111)
int param111;      /* add this line */
{
    printf("%d\n", param111);
    return param111;
}

int main(void)     /* add "void" */
{
    int bla0 = func(99);
    int bla1 = func(10,99);
    int bla2 = func(11111110,99,10001);
    printf("%d, %d, %d\n", bla0, bla1, bla2);
}

не нарушает правил, требующих диагностики времени компиляции в C90, C99 или C11, но второй и третий вызовы func имеют поведение undefined.

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

Ответ 7

Если вы проверяете предупреждения при компиляции, вы увидите это сообщение:

zeroparam.c:2: warning: type of ‘param111’ defaults to ‘int’

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