【问题标题】:C: type conversion when passing an argument on a function callC:在函数调用中传递参数时的类型转换
【发布时间】:2010-11-21 19:01:54
【问题描述】:

来自 C 编程语言第 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 和 number 在函数调用中都被转换为 int 和 double,然后又被转换回 char 和 float。真的是这样吗?

【问题讨论】:

  • 这是一个很好的问题 - 也说明了为什么始终在范围内拥有一个原型并声明所有参数很重要。
  • “转换回来”?为什么? cnumber 的值在传递给函数时被强制转换为 intdouble 类型,但没有什么可以“转换回来”。

标签: c type-conversion


【解决方案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

这就是演员表不重要的原因。考虑以下代码 sn-p:

signed char c = 'a';
int i = (short) c;

这里 c 的值首先提升为 short(显式),然后提升为 int(隐式)。 i 的值将始终是 int

至于charshort 变成intfloat 变成double 是指隐式函数原型的默认类型。当编译器在看到原型或函数定义之前看到对函数的调用时,它会自动生成原型。整数值默认为int,浮点值默认为double

如果最终的函数声明与隐式原型不匹配,您将收到警告。

【讨论】:

  • 您的帖子回答了我在发布此问题之前提出的许多问题。
  • 这个答案混合了“原型”和“声明”。原型是声明函数参数类型的东西。声明可能包括原型和整个事物的名称(“void f();”)。所以你想说“隐式声明”和“显式声明”。
【解决方案2】:

您已经大致了解出了什么问题,但不完全是。

你写的时候发生了什么

function_call(c, number);

编译器看到你正在调用一个它还没有看到的函数,因此必须决定它的签名应该是什么。根据您之前引用的提升规则,它将 char 提升为 int 并将 float 提升为 double 并确定签名为

function_call(int, double)

然后当它看到

function_call(char c, float f)

它将此解释为具有相同名称的不同函数的签名,这在 C 中是不允许的。这与您对函数的原型与实际定义方式不同的错误完全相同,只是在这种情况下原型由编译器隐式生成。

因此,正是这条规则导致了问题,但该错误与在类型之间实际来回转换值没有任何关系。

【讨论】:

  • 升级也发生在旧式 (K&R) 声明以及可变参数函数中。
  • 我不认为这个问题与错误本身有关,尽管这两者肯定是相关的。
  • 我看到的唯一问号附在一个句子上,询问他对错误原因的解释是否正确。但是,是的,这个问题肯定有几点要回答。
【解决方案3】:

编译器抱怨它假定 function_call 是标准指示的 int 返回函数,然后您告诉它是 void 函数。除非您明确声明它们与实际函数不同,否则编译器不会关心参数。你可以不传递任何参数,它也不会抱怨。

您必须始终声明您的函数,因为如果该函数位于其他模块中,则不会检测到此错误。如果函数应该返回任何可能大于 int 的类型,例如 void* 或 long,则在调用函数中强制转换为 int 很可能会截断它,给您留下一个奇怪的错误。

【讨论】:

  • 编译器错误与他传递的参数无关。你以为它有,你把我改装了。其他答案忽略了这一点,并使 OP 认为他的编译器不会捕获错误的参数错误。随便。
【解决方案4】:

每个人都错过了一件事。在 ISO C 中,ISO 语法原型会覆盖默认参数提升。

在这种情况下,编译器可以根据定义的风格生成不同的代码(!)。这可以让您与 K&R 兼容,但您不能总是在语言级别之间调用,除非您按照 K&R 的预期编写了 ISO 代码或修改了 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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-10-11
    • 2012-12-16
    • 1970-01-01
    • 2014-02-06
    • 2019-01-09
    • 2020-12-25
    • 1970-01-01
    • 2023-03-31
    相关资源
    最近更新 更多