gpt4 book ai didi

c - 没有 `...` 的可变参数函数

转载 作者:行者123 更新时间:2023-12-02 06:47:54 25 4
gpt4 key购买 nike

在 x86_64/Linux 上,用 gcc/clang -O3 编译:

void void_unspec0(),void_unspec1(),void_unspec2(),void_unspec3(),void_void(void);

void call_void_void()
{
void_void();
void_void();
void_void();
void_void();
void_void();
}

void call_void_unspec()
{
void_unspec0();
void_unspec0();
void_unspec0();
void_unspec0();
void_unspec1(.0,.0,.0);
void_unspec2(.0,.0,.0,.0,.0,.0,.0,.0);
void_unspec3(.0,.0,.0,.0,.0,.0,.0,.0,.0,.0);
}

反汇编为:

0000000000000000 <call_void_void>:
0: 48 83 ec 08 sub $0x8,%rsp
4: e8 00 00 00 00 callq 9 <call_void_void+0x9>
9: e8 00 00 00 00 callq e <call_void_void+0xe>
e: e8 00 00 00 00 callq 13 <call_void_void+0x13>
13: e8 00 00 00 00 callq 18 <call_void_void+0x18>
18: 48 83 c4 08 add $0x8,%rsp
1c: e9 00 00 00 00 jmpq 21 <call_void_void+0x21>
21: 66 66 2e 0f 1f 84 00 data16 nopw %cs:0x0(%rax,%rax,1)
28: 00 00 00 00
2c: 0f 1f 40 00 nopl 0x0(%rax)

0000000000000030 <call_void_unspec>:
30: 48 83 ec 08 sub $0x8,%rsp
34: 31 c0 xor %eax,%eax
36: e8 00 00 00 00 callq 3b <call_void_unspec+0xb>
3b: 31 c0 xor %eax,%eax
3d: e8 00 00 00 00 callq 42 <call_void_unspec+0x12>
42: 31 c0 xor %eax,%eax
44: e8 00 00 00 00 callq 49 <call_void_unspec+0x19>
49: 31 c0 xor %eax,%eax
4b: e8 00 00 00 00 callq 50 <call_void_unspec+0x20>
50: 66 0f ef d2 pxor %xmm2,%xmm2
54: b8 03 00 00 00 mov $0x3,%eax
59: 66 0f ef c9 pxor %xmm1,%xmm1
5d: 66 0f ef c0 pxor %xmm0,%xmm0
61: e8 00 00 00 00 callq 66 <call_void_unspec+0x36>
66: 66 0f ef ff pxor %xmm7,%xmm7
6a: b8 08 00 00 00 mov $0x8,%eax
6f: 66 0f ef f6 pxor %xmm6,%xmm6
73: 66 0f ef ed pxor %xmm5,%xmm5
77: 66 0f ef e4 pxor %xmm4,%xmm4
7b: 66 0f ef db pxor %xmm3,%xmm3
7f: 66 0f ef d2 pxor %xmm2,%xmm2
83: 66 0f ef c9 pxor %xmm1,%xmm1
87: 66 0f ef c0 pxor %xmm0,%xmm0
8b: e8 00 00 00 00 callq 90 <call_void_unspec+0x60>
90: 66 0f ef c0 pxor %xmm0,%xmm0
94: 6a 00 pushq $0x0
96: 66 0f ef ff pxor %xmm7,%xmm7
9a: 6a 00 pushq $0x0
9c: 66 0f ef f6 pxor %xmm6,%xmm6
a0: b8 08 00 00 00 mov $0x8,%eax
a5: 66 0f ef ed pxor %xmm5,%xmm5
a9: 66 0f ef e4 pxor %xmm4,%xmm4
ad: 66 0f ef db pxor %xmm3,%xmm3
b1: 66 0f ef d2 pxor %xmm2,%xmm2
b5: 66 0f ef c9 pxor %xmm1,%xmm1
b9: e8 00 00 00 00 callq be <call_void_unspec+0x8e>
be: 48 83 c4 18 add $0x18,%rsp
c2: c3 retq

在第二种情况下 (call_void_unspec()),编译器计算寄存器中传递的浮点参数,大概是因为 SysVABI/AMD64 spec说应该。

When a function taking variable-arguments is called, %rax must be set to the total number of floating point parameters passed to the function in SSE registers

ABI 规范中的规则的原因是什么?鉴于用 ...(省略号)定义的函数是 required to be prototyped (6.5.2.2p6),因此非原型(prototype)函数调用必须遵守它打电话前?没有 ... 的函数也可以是可变的吗?

最佳答案

C 标准所说的

请注意,可变参数函数只能在原型(prototype)存在时调用。如果您尝试在原型(prototype)不存在的情况下调用 printf(),则会出现 UB(未定义行为)。

C11 §6.5.2.2 Function calls ¶6说:

¶6 If the expression that denotes the called function has a type that does not include a prototype, the integer promotions are performed on each argument, and arguments that have type float are promoted to double. These are called the default argument promotions. If the number of arguments does not equal the number of parameters, the behavior is undefined. If the function is defined with a type that includes a prototype, and either the prototype ends with an ellipsis (, ...) or the types of the arguments after promotion are not compatible with the types of the parameters, the behavior is undefined. If the function is defined with a type that does not include a prototype, and the types of the arguments after promotion are not compatible with those of the parameters after promotion, the behavior is undefined, except for the following cases:

  • one promoted type is a signed integer type, the other promoted type is the corresponding unsigned integer type, and the value is representable in both types;
  • both types are pointers to qualified or unqualified versions of a character type or void.

应用于问题中的原始代码

问题中的原始代码与此类似——连续的相同函数调用已减少为单个调用。

void void_unspec(), void_void(void);

void call_void_void()
{
void_void();
}

void call_void_unspec()
{
void_unspec();
void_unspec(.0,.0,.0);
void_unspec(.0,.0,.0,.0,.0,.0,.0,.0);
void_unspec(.0,.0,.0,.0,.0,.0,.0,.0,.0,.0);
}

此代码调用 UB,因为函数调用 void_unspec() 的参数数量并不完全匹配它被定义为采用的参数数量(无论定义是什么;它不能同时取 0、3、8 和 10 个参数)。这不是约束冲突,因此不需要诊断。编译器通常会做任何它认为对向后兼容最好的事情,并且通常不会导致彻底的崩溃,但是由于程序员违反标准规则而产生的任何问题。

而且因为标准说行为是未定义的,所以没有特定的理由要求编译器必须设置 %rax(当然,C 标准对 %rax< 一无所知), 但简单的一致性表明它应该。

应用于题中修改后的代码

题目中的代码修改成这样(重复连续调用再次省略):

void void_unspec0(), void_unspec1(), void_unspec2(), void_unspec3(), void_void(void);

void call_void_void()
{
void_void();
}

void call_void_unspec()
{
void_unspec0();
void_unspec1(.0,.0,.0);
void_unspec2(.0,.0,.0,.0,.0,.0,.0,.0);
void_unspec3(.0,.0,.0,.0,.0,.0,.0,.0,.0,.0);
}

代码不再不可避免地调用未定义的行为。但是,在定义了 void_unspec0() 等函数的地方,它们应该类似于:

void void_unspec0(void) { … }
void void_unspec1(double a, double b, double c) { … }
void void_unspec2(double a, double b, double c, double d, double e, double f, double g, double h) { … }
void void_unspec3(double a, double b, double c, double d, double e, double f, double g, double h, double i, double j) { … }

一个等效的符号是:

void void_unspec2(a, b, c, d, e, f, g, h)
double a, b, c, d, e, f, g, h;
{

}

这是使用 K&R 预标准非原型(prototype)定义。

如果函数定义与这些不匹配,则 §6.5.2.2¶6 表示调用的结果是未定义的行为。这样一来,标准就不必对各种可疑情况下发生的事情进行立法。和以前一样,编译器可以随意传递 %rax 中的 float ;这就说得通了。但是,在争论将会发生什么方面几乎无能为力——要么调用符合定义并且一切正常,要么它们不匹配并且存在未指定(和无法指定)的潜在问题。

请注意,call_void_void()call_void_unspec() 都没有使用原型(prototype)定义。它们都是采用零参数的函数,但没有可见的原型(prototype)强制执行,因此同一文件中的代码可以调用 call_void_void(1, "abc") 而不会引起编译器的提示。 (在这方面,与许多其他方面一样,C++ 是一种不同的语言,具有不同的规则。)

关于c - 没有 `...` 的可变参数函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53199914/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com