2010-11-14 6 views
3

Действительно ли это код C (C99)?Является ли это сомнительным использование объявления не-прототипа функции?

int f(); 
int g(int x) 
{ 
    if (x<0) return f(x); 
    else return f(x,x); 
} 

Очевидно, что программа имеет неопределенное поведение, если g когда-либо вызывается с отрицательным аргументом и f не функция, которая принимает один int аргумента, или если g когда-либо вызывается с неотрицательным аргументом и f является не функция, которая принимает два аргумента int. Но иначе?

Рассмотрим в качестве примера этот отдельный исходный файл, который вызывает g из вышеизложенного и обеспечивает f:

int g(); 
#ifdef FOO 
int f(int a, int b) { return a+b; } 
int main() { return g(1); } 
#else 
int f(int a) { return a; } 
int main() { return g(-1); } 
#endif 
+0

Это даже не компилируется, верно? Не говоря уже о связи. –

+3

@ Oli Charlesworth: Вы пробовали? Он компилирует и связывает меня. (Без ошибок или предупреждений с помощью 'gcc -std = c99 -Wall -Wextra -pedantic') –

+1

@Oli: Возможно, вы сбиваете с толку C и C++? Конечно, нет никакого реального линкера, который дистанционно заботится о сигнатуре функции C во время связи. И если использование в моем вопросе действительно, что, я подозреваю, это так, то это доказательство того, что линкеру не разрешают заботиться о подписи. –

ответ

4

Давайте попробуем наоборот: Зачем ему не?. Я действительно не могу найти никаких аргументов или правил, запрещающих вышеуказанный код. Вызов функции в соответствующей другой ветке никогда не выполняется (хотя обсуждение в комментариях указывает, что это не так просто!).

+0

В зависимости от аргумента 'array [n]' может быть действительным, даже если 'n' имеет отрицательное значение. Это был бы более убедительный пример, если 'array', где массив, а не' указатель'. –

+0

@Charles хороший пункт. Я изменил ответ. –

+0

Что ж, если я объявил 'int f(), h();' и выполнил 'if (x <0) return f (x); else return h (x); 'и гарантирует, что' x' всегда отрицательный, но никогда не определяемый 'h'? Я ожидал бы ошибки времени ссылки, если бы компилятор не смог оптимизировать случай, когда 'x' является неотрицательным. Это немного отличается от моего вопроса, но я пытаюсь точно определить, в чем разница. –

0

Что делать, если это f(int x, ...) и смотрит на знак своего первого аргумента, чтобы узнать, сколько (0 или 1) varargs он получил?

+2

Тогда это неопределенное поведение, потому что функция объявляется с помощью ', ...' и вызывается без прототипа. –

+1

Что сказал Чарльз. Вариадические функции ** абсолютно требуют ** прототипа. –

+0

Возможно, более интересно, что, если это 'f()', а знак первого аргумента сообщает ему, сколько параметров у него есть, и функция фактически реализована в ассемблере, а не C, со знанием соглашения C-вызова, так что он может читать правильные аргументы без использования varargs? Если этот код недействителен, вы не можете этого сделать, но, как говорит Р. в комментариях выше, ни один компоновщик на практике не будет препятствовать этому. –

0

Действительно (это зависит от того, какой стандарт вы используете). Вы должны прочитать что-нибудь около calling conventions.

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

Рассмотрим этот кусок кода:

int f(int x, int y); 
int g(int x) 
{ 
    int k; //No value 
    if (x<0) return f(x, k); 
    else return f(x, x); 
} 

Конечно, это плохая идея. Вы должны явно указать все аргументы.

Вы также можете использовать int f(void);, чтобы явно объявить, что f не принимает аргументов.

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

+2

'int f (...);' полностью недействителен. Невозможно написать функцию в стандарте C, которая имеет свои собственные аргументы «...» и прототипирует невариантную функцию, как если бы она была вариационной или наоборот, или вообще не могла прототипировать вариационную функцию , все это приводит к неопределенному поведению. –

+4

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

+0

Спасибо, Чарльз. Я бы дал этот комментарий +10, если мог, потому что вы высказали мой вопрос лучше в одном предложении, чем я сделал по всему оригинальному вопросу. –

3

C99 (6.5.2.2 Функциональные вызовы, пункт 8) говорит, что количество и типы параметров и аргументов «не сравниваются», если определение функции не имеет прототипа.

Я видел это (ab), используемое в дикой природе с указателями функций. Массив из void (*)() содержал как указатели функций void (*)(struct Client *), так и void (*)(struct Client *, int parc, char *parv[]). На основе индекса массива код передал дополнительные параметры или нет.

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

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

2

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

Существует другой, более простой, способ прийти к вашему выводу о линкере, хотя: Так как это разрешено:

int f(); 
int (*fp)() = f; 

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

+0

+1 хороший пример –

+0

Мне кажется, что это, скорее всего, почему C++ изменил значение 'int f();' - он должен был, чтобы позволить перегрузку функции. – caf