C: преобразование типов при передаче аргумента при вызове функции - PullRequest
15 голосов
/ 22 августа 2009

Из языка программирования Си, 2-е издание:

Поскольку аргумент вызова функции является выражением, преобразования типов также имеют место, когда аргументы передаются в функцию. В отсутствие прототипа функции 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: предупреждение: предыдущее неявное объявление «function_call» было здесь

Я предполагаю, что c и число были преобразованы в int и double при вызове функции, а затем преобразованы обратно в char и float. Это то, что на самом деле произошло?

Ответы [ 4 ]

19 голосов
/ 22 августа 2009

Приведения не имеют значения, имеет значение (возможно, неявный) прототип.

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 для значений с плавающей запятой.

Если объявление потенциальной функции не соответствует неявному прототипу, вы получите предупреждения.

16 голосов
/ 22 августа 2009

У вас есть общее представление о том, что не так, но не совсем.

Что случилось, когда вы написали

function_call(c, number);

Компилятор увидел, что вы вызываете функцию, которую он еще не видел, и поэтому должен был решить, какой должна быть ее подпись. На основании правила продвижения, которое вы цитировали ранее, он повысил значение char до int, а значение float - double и решило, что подпись

function_call(int, double)

Тогда, когда он видит

function_call(char c, float f)

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

Итак, именно это правило вызывает проблему, но ошибка не имеет ничего общего с фактическим преобразованием значений между типами.

2 голосов
/ 22 августа 2009

Все упустили одну вещь.В ISO C прототип синтаксиса ISO переопределяет продвижение аргумента по умолчанию.

И в этом случае компилятору разрешено генерировать другой код (!) , основываясь исключительно на стиле определения.Это обеспечивает совместимость с K & R, но вы не всегда можете звонить между уровнями языка, если вы не написали код ISO, как ожидает K & R, или не изменили код K & R, чтобы увидеть прототипы ISO.-О ...

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
2 голосов
/ 22 августа 2009

Компилятор жалуется на то, что он предположил, что function_call является функцией, возвращающей int, как указано в стандарте, а затем вы говорите, что это void-функция. Компилятор не будет заботиться об аргументах, если вы явно не объявите их отличными от фактической функции. Вы можете передать это без аргументов, и он не будет жаловаться.

Вы должны всегда объявлять свои функции, потому что эта ошибка останется незамеченной, если функция находится в других модулях. Если функция должна возвращать любой тип, который может быть больше, чем int, например void * или long, приведение к int в функции вызывающей стороны, скорее всего, обрезает ее, оставляя странную ошибку.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...