C: преобразование типа при передаче аргумента при вызове функции
Из языка программирования C 2nd Edition:
Поскольку аргумент вызова функции является выражением, преобразования типов также имеют место, когда аргументы передаются функции. В отсутствие прототипа функции char и short становятся int, а float становится double.
Прочитав текст, создается впечатление, что если вы явно не укажете тип аргумента с использованием прототипа прототипа или функции, аргументы функции всегда будут передаваться как переданные как int или double.
Чтобы проверить мое предположение, я скомпилировал следующий код:
#include <stdio.h>
main()
{
unsigned char c = 'Z';
float number = 3.14f;
function_call(c, number);
}
void function_call(char c, float f)
{
}
После компиляции я получаю следующие предупреждения:
typeconversion.c: 11: предупреждение: конфликтующие типы для 'function_call
typeconversion.c: 7: warning: предыдущее неявное объявление 'function_call было здесь
Моя догадка - это с, а число было преобразовано в int и double в вызове функции, а затем было преобразовано обратно в char и float. Это то, что на самом деле произошло?
Ответы
Ответ 1
Броски не имеют значения, это важно (возможно, неявный) прототип.
void foo(short s) {
// do something
}
int main(void) {
signed char c = 'a';
foo(c); // c is promoted to short by explicit prototype
bar(c); // c is promoted to int by implicit prototype
}
void bar(int i) {
// do something
}
Когда в книге говорится, что "аргумент вызова функции является выражением", это означает, что применяются правила продвижения такого же типа. Это может быть проще понять, если вы думаете о аргументе функции как неявное присвоение переменной, указанной в прототипе функции. например в вызове foo()
выше есть неявный short s = c
.
Вот почему броски не имеют значения. Рассмотрим следующий фрагмент кода:
signed char c = 'a';
int i = (short) c;
Здесь значение c сначала увеличивается до short
(явно), затем до int
(неявно). Значение i
всегда будет int
.
Что касается char
и short
, становящихся int
и float
, становясь double
, что относится к типам по умолчанию для прототипов неявных функций. Когда компилятор видит вызов функции, прежде чем он увидит либо прототип, либо определение функции, он автоматически генерирует прототип. По умолчанию значение int
для целых значений и double
для значений с плавающей запятой.
Если объявление возможной функции не соответствует неявному прототипу, вы получите предупреждения.
Ответ 2
У вас есть общее представление о том, что неправильно, но не совсем.
Что случилось, когда вы написали
function_call(c, number);
Компилятор увидел, что вы вызываете функцию, которую он еще не видел, и поэтому должен был решить, какова должна быть его подпись. Основываясь на правиле продвижения, которое вы цитировали ранее, он продвигал char до int и float, чтобы удвоить и решил, что подпись
function_call(int, double)
Затем, когда он видит
function_call(char c, float f)
он интерпретирует это как сигнатуру для другой функции с тем же именем, которая не допускается в C. Это точно такая же ошибка, как если бы вы прототипили функцию иначе, чем вы ее определяете, просто в этом случае прототип неявно генерируется компилятором.
Итак, именно это правило вызывает проблему, но ошибка не имеет ничего общего с фактическим преобразованием значений между типами.
Ответ 3
Компилятор жалуется, что он предположил, что функция function_call является функцией возврата int, как указано стандартом, а затем вы говорите, что это функция void. Компилятор не заботится о аргументах, если вы явно не заявляете, что они отличаются от фактической функции. Вы не можете передавать ему никаких аргументов, и он не будет жаловаться.
Вы всегда должны объявлять свои функции, потому что эта ошибка будет не обнаружена, если функция находится в других модулях. Если функция должна возвращать любой тип, который может быть больше, чем int, например void * или long, приведение в int в функции вызывающего абонента, скорее всего, усекает его, оставив вас с необычной ошибкой.
Ответ 4
Все пропустили одно. В ISO C прототип ISO-синтаксиса отменяет продвижение аргументов по умолчанию.
И в этом случае компилятору разрешается генерировать другой код (!), основанный исключительно на стиле определения. Это дает вам совместимость с K & R, но вы не можете всегда звонить между языковыми уровнями, если только вы не написали код ISO, поскольку K & R ожидает или изменил код K & R, чтобы увидеть ISO-прототипы.
Попробуйте с помощью cc -S -O...
extern float q; void f(float a) { q = a; }
movl 8(%ebp), %eax
movl %eax, q
extern float q; void f(a) float a; { q = a; } // Not the same thing!
fldl 8(%ebp)
fstps q