完全忽略C函数的可变参数安全吗?

完全忽略C函数的可变参数安全吗?,c,variadic-functions,C,Variadic Functions,我有一个大致如下的函数: typedef struct SomeType { ... } SomeType; void TakesArgs(SomeType *t1, ...) { // iterates through arguments } // usage: TakesArgs(&a, &b, &c); 如果我将TakesArgs更改为no-op,同时仍然保持所有调用代码不变,我是否会在内存方面(或其他方面)遇到任何奇怪的风险 void Take

我有一个大致如下的函数:

typedef struct SomeType {
  ...
} SomeType;

void TakesArgs(SomeType *t1, ...) {
     // iterates through arguments
}

// usage:
TakesArgs(&a, &b, &c);
如果我将
TakesArgs
更改为no-op,同时仍然保持所有调用代码不变,我是否会在内存方面(或其他方面)遇到任何奇怪的风险

void TakesArgs(SomeType *t1, ...) {
    return;
}

// usage unchanged: 
TakesArgs(&a, &b, &c);

换句话说,跳过原始实现中执行的
va_列表
/
va_开始
舞蹈是否会产生奇怪的副作用?

是的,它是完全安全的。仅仅为了阅读变量参数,不需要阅读所有变量参数。您甚至不需要在
TakesArgs
中启动可变参数读取序列


在后台,它通常意味着执行任何参数传递维护任务的负担被赋予了调用代码(它们以通常被认为是“传统的”C调用约定的方式工作)。被叫方不必做任何事情。

[根据评论编辑。]


在某些系统中,默认情况下,弹出参数是被调用方的工作。那显然会给你带来麻烦。但是,正是因为这个原因,变量函数必须正确声明,并且声明必须对调用方正确可见。原因是在这些系统上,可变函数必须使用非默认调用序列,调用方弹出参数,因为只有调用方才能确定有多少参数。因此,只要正确声明了函数,您就应该是安全的。

我相信这是安全的,尽管标准在这一点上并不十分明确

7.16第3段说:

如果需要访问可变参数,则调用的函数 应声明一个对象(在本规范中通常称为
ap
) 子条款)具有类型
va_列表

注意这句话的开头:“如果需要访问不同的参数”。这意味着,如果不需要访问不同的参数,就不需要声明
va_list
对象——这将使调用
va_start
va_arg
va_end
成为不可能

一个可能的反驳是
va_end
(N1570 7.16.1.3)的描述中说:

如果没有相应的调用
va\u start
va\u copy
宏,或者如果在返回之前未调用
va_end
宏,则 行为是不确定的


但是,考虑到上下文,我认为如果在调用
va\u start
后未调用
va\u end
则适用该方法。

可变函数不是内存管理的一部分。这是完全安全的。我不愿意把它变成一个答案,因为我不能严格地备份它,但它绝对是安全的。我不认为会有奇怪的副作用,因为编译器会生成在堆栈上为&a、&b和&c保留一些空间的代码,然后像正常的函数调用一样将它们弹出(不管传递的参数有多少)。但是,我对此并不完全确定。在
\uu stdcall
中,被调用方的工作是清理堆栈。这不是“回到过去”,因为
\uu stdcall
仍然被广泛使用。在
cdecl
中,调用者负责清理堆栈。这并不奇怪,但例如,Pascal语言的标准。这是有充分理由的。缺点是被调用者必须首先弹出返回地址。除非CPU有特殊的返回和释放指令,例如。g、 作为680xx家族。随着RISC体系结构的兴起,这一切都消失了。谢谢。我现在记起来了。我更新了我的答案。@abelenky:我知道as
pdecl
as in
Pascal
。IIRC,gcc对此有一些属性。@Olaf:x86也有相关说明;
RET 12
,例如,从stac弹出返回地址然后,像往常一样,将另一个12字节的参数弹出到位桶中。@Olaf:具体的措辞是什么?ISO/IEC 9899:2011§7.16.1.3
va_end
^2说:
va_end
宏有助于函数的正常返回,该函数的变量参数列表是通过扩展
va_start
宏引用的,或者包含
va_copy
宏扩展的函数,用于初始化
va_列表ap
va_end
宏可以修改
ap
,使其不再可用(无需通过
va_start
va_copy
宏重新初始化)。如果没有相应调用
va_start
va_copy
宏,或者如果在返回之前没有调用
va_end
宏,则行为未定义。该措辞表示“如果有
va_start
,则必须有
va_end
”,并且“没有
va_start
,就不能有
va_end
”,但不一定有
va_start
。关键字对应意味着,如果在调用
va_start
之前调用
va_end
,就会出现问题;这并不意味着必须调用
va_start
(但如果不这样做,也不能调用
va_end
)(为了方便起见,我跳过
va_copy
,但您可以适当地插入。)@Olaf:这一措辞明确地指调用
va_end
,之前没有调用
va_start
/
va_copy
。它是UB。它还指调用
va_start
/
va_copy
,但没有终止
va_end
。它也是UB。但它没有说什么不调用
va>_开始
va_-end
。关于7.16.1.3,我认为这意味着“如果您在没有先调用
va_-start
的情况下调用
va_-end
”。