Объясните, Как прогр работает

#include<stdio.h>
int f();

int main()
{

    f(1);
    f(1,2);
    f(1,2,3);
}

f(int i,int j,int k)
{

    printf("%d %d %d",i,j,k);

}

это хорошо работает (без какой-либо ошибки)... u, может объяснить, как это выполняется? как f (1) и f (1,2) ссылки на f (интервал, интервал, интервал)?

7
задан Chris Cooper 5 July 2010 в 06:33
поделиться

6 ответов

У вас должно быть другое определение "ошибки", чем у меня :-) Что выводится при первых двух вызовах вашей функции f? Я получаю

1 -1216175936 134513787
1  2          134513787
1  2          3

для моих трех вызовов функции.

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

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

Это вполне компилируемый, хотя и очень неразумный Си. И я имею в виду это в самом настоящем смысле слова "неопределенное поведение" (ссылаясь конкретно на C99: "Если выражение, обозначающее вызываемую функцию, имеет тип, не включающий прототип, ... если число аргументов не равно числу параметров, поведение неопределено").

Вам действительно следует предоставлять полностью сформированные прототипы функций, такие как:

void f(int,int,int);

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


В качестве отступления, что обычно происходит под прикрытием, так это то, что вызывающая функция начинает со стека, как:

12345678
11111111

и выталкивает (например) два значения на стек, так что в итоге получается:

12345678
11111111
2
1

Когда вызываемая функция использует первые три значения на стеке (поскольку это то, что она хочет), она обнаруживает, что у нее есть 1, 2 и 11111111.

Она делает то, что должна сделать, затем возвращается, и вызывающая функция убирает эти два значения из стека (это называется стратегией "вызывающий делает хорошее"). Горе тому, кто попытается сделать это со стратегией "callee-makes-good" :-), хотя это довольно необычно в C, поскольку это делает функции с переменным аргументом, такие как printf, немного сложными для выполнения.

14
ответ дан 6 December 2019 в 08:14
поделиться

Это объявление:

int f();

... говорит компилятору "f - это функция, которая принимает некоторое фиксированное количество аргументов и возвращает int". Затем вы пытаетесь вызвать ее с одним, двумя и тремя аргументами - компиляторы языка Си концептуально однопроходные (после препроцессирования), поэтому в этот момент у компилятора нет информации, чтобы спорить с вами.

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

4
ответ дан 6 December 2019 в 08:14
поделиться
int f();

В Си это объявляет функцию, которая принимает переменное количество аргументов, т.е. в C++ это эквивалентно следующему

int f(...);

Чтобы проверить это, используйте следующее вместо int f();

int f(void);

Это вызовет недовольство компилятора.

Обратите внимание: здесь также задействована причуда компоновщика языка Си... компоновщик языка Си не проверяет аргументы, передаваемые функции в момент вызова, и просто ссылается на первый открытый символ с тем же именем. Таким образом, использование f() в main разрешено из-за объявления int f(). Но компоновщик связывает функцию f(int, int, int) во время компоновки в местах вызова. Надеюсь, это имеет какой-то смысл (пожалуйста, дайте мне знать, если это не так)

3
ответ дан 6 December 2019 в 08:14
поделиться

Когда вы компилируете ту же программу с помощью компилятора g ++, вы видите следующие ошибки -

g++ program.c
program.c: In function `int main()':
program.c:2: error: too many arguments to function `int f()'
program.c:6: error: at this point in file
program.c:2: error: too many arguments to function `int f()'
program.c:7: error: at this point in file
program.c:2: error: too many arguments to function `int f()'
program.c:8: error: at this point in file
program.c: At global scope:
program.c:12: error: ISO C++ forbids declaration of `f' with no type

Использование gcc с опцией -std = c99 просто выдает предупреждение

Скомпилируйте ту же программу с тем же стандарт, который g ++ имеет по умолчанию, дает следующее сообщение:

gcc program.c -std=c++98
cc1: warning: command line option "-std=c++98" is valid for C++/ObjC++ but not for C

Я бы ответил, что компиляторы c соответствуют другому стандарту, который не столь строг, как тот, которому соответствует c ++.

0
ответ дан 6 December 2019 в 08:14
поделиться

Он работает нормально, поскольку int f () означает то, что уже сказано в другом ответе: это означает неопределенное количество аргументов. Это означает, что вы можете вызвать его с нужным количеством аргументов (также более 3), при этом компилятор ничего не скажет об этом.

Причина, по которой он работает «под прикрытием», заключается в том, что аргументы помещаются в стек, а затем доступны «из» стека в функции f . Если вы передадите 0 аргументов, i, j, k функции «соответствуют» значениям в стеке, которые, согласно функции PoV, являются мусором. Тем не менее, вы можете получить доступ к их ценностям. Если вы передаете 1 аргумент, один из трех i j k обращается к значению, остальные получают мусор. И так далее.

Обратите внимание, что те же рассуждения работают, если аргументы передаются каким-либо другим способом, но в любом случае это используемое соглашение. Другой важный аспект этих соглашений заключается в том, что вызываемый объект не отвечает за настройку стека; это зависит от вызывающего, который знает, сколько аргументов было выдвинуто на самом деле.Если бы это было не так, определение f могло бы предполагать, что он должен «отрегулировать» стек так, чтобы «освободить» три целых числа, и это могло бы вызвать какой-то сбой.

То, что вы написали, подходит для текущего стандарта (при компиляции gcc без предупреждений даже с -std = c99 -pedantic ; предупреждение есть, но оно касается отсутствующего int перед определением f ), хотя многие люди находят это отвратительным и называют это «устаревшей функцией». Конечно, ваше использование в примере кода не показывает никакой полезности, и, вероятно, это может помочь устранить ошибки и использовать более связывающие прототипы! (Но, тем не менее, я предпочитаю C вместо Ada)

add

Более «полезное» использование «функции», которое не вызывает проблему «неопределенного поведения», может быть

#include<stdio.h>
int f();

int main()
{

    f(1);
    f(2,2);
    f(3,2,3);
}

int f(int i,int j,int k)
{
  if ( i == 1 ) printf("%d\n", i);
  if ( i == 2 ) printf("%d %d\n", i, j);
  if ( i == 3 ) printf("%d %d %d\n", i, j, k);
}
1
ответ дан 6 December 2019 в 08:14
поделиться

В языке Си объявление должно декларировать, по крайней мере, возвращаемый тип. Так

int f();

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

f(int i,int j,int k)
{

    printf("%d %d %d",i,j,k);
}

Теперь известно, что функция принимает три intа. Если вы вызовете функцию с аргументами, отличными от определения, вы получите не ошибку времени компиляции, а ошибку времени выполнения (или, если вам не нравится негативный оттенок ошибки: "неопределенное поведение"). Компилятор языка Си не принуждается стандартом отлавливать эти несоответствия.

Чтобы предотвратить эти ошибки, вы должны использовать правильные прототипы функций, такие как

f(int,int,int);           //in your case
f(void);                  //if you have no parameters
0
ответ дан 6 December 2019 в 08:14
поделиться
Другие вопросы по тегам:

Похожие вопросы: