我在C语言中摆弄可变参数函数来学习它们是如何工作的,并且试图构建一个简单的'print lines'函数,而不需要手动计算行数。我将该函数 Package 在一个宏中,该宏在char *
参数列表的末尾添加一个null指针,这样该函数就可以逐行打印,直到找到一个null参数。
我知道我已经避免了一些常见的陷阱,比如忘记在参数列表中转换null指针,但是不管什么原因,这段代码仍然不能工作,调用带有任意数量参数的函数都能正确地打印它们,然后无法检测到null,打印出一堆垃圾数据,最后崩溃。
int printline(const char *str) {
printf("%s\n", str);
}
#define printlines(...) _comments(__VA_ARGS__, (char*)0)
int _printlines(char* first, ...) {
if (first) {
printline(first);
va_list ptr;
va_start(ptr, first);
char *next;
do {
char *next = va_arg(ptr, char *);
if (next) {
printline(next);
}
} while(next);
va_end(ptr);
}
}
int main() {
printlines("hi");
//prints 'hi', then prints garbage data and crashes
printlines("how", "are", "you");
//prints 'how', 'are', and 'you', then prints garbage data and crashes
_printlines("help", (char *)0);
//prints 'help', then prints garbage data and crashes
_printlines("something", "is", "wrong", (char *)NULL);
//prints 'something', 'is', and 'wrong', then prints garbage data and crashes
}
5条答案
按热度按时间ukqbszuj1#
如果你看看这个:
你会看到有两个独立的变量
next
,do..while
循环内部的变量屏蔽了外部定义的变量,你把va_arg
的结果赋给内部的next
,然后当你得到while (next)
条件时,内部的next
超出了作用域,您现在正在阅读外部的next
,它从未被写入。这将触发undefined behavior。相反,您需要:
所以你只需要使用一个名为
next
的变量。gywdnpxw2#
小的重写。宏被修改为+0,所以它可以不带参数。
a9wyjsp73#
保存时间,启用所有编译器警告。
warning: 'next' may be used uninitialized [-Wmaybe-uninitialized] } while(next);
很快就触及了关键问题。warning: control reaches end of non-void function [-Wreturn-type]
在2个位置。这比在堆栈溢出时发布要快。
ukxgm1gy4#
“垃圾”来自未初始化的对象
next
。当退出循环时,循环中定义的另一个next
将停止存在。删除奇怪的功能和清理一些混乱,你可以得到正确的。
5uzkadbs5#
我强烈建议你避免可变参数函数,而使用指针数组和可变参数宏(带有终止符对象)。
当使用这种方法时,您的函数将如下所示:
不仅变元函数有时很难编码,而且变元函数的ABI也有问题,不同的语言可能会以不同的方式处理它,不同语言之间的C绑定可能会破坏您的代码。
此外,当使用这种方法时,事情也会变得更加有趣和有趣,允许轻松的类型检测和多类型参数...... www.example.com CSTL库中的这段代码facil.io为我的意思提供了一个很好的示例。
该函数接受结构体数组:
然后,宏确保数组的最后一个元素是终止符元素:
提供了额外的帮助宏,以使
fio_string_write_s
结构更易于构造,即:并且该函数使用终止符元素来检测宏接收到的参数的数量:
使用示例(来自源代码注解):
这既简化了代码,又避免了变元函数的许多问题,还允许其他语言的C绑定更好地工作,并且结构数组的构造方式更适合特定的目标。