将非POD传递给变量参数函数(如printf)是未定义的行为(1,2),但我不明白为什么C标准是这样设置的 . 变量arg函数中是否存在任何固有的东西阻止它们接受类作为参数?
变量arg callee确实对它们的类型一无所知 - 但它也不知道它接受的内置类型或普通POD .
而且,这些必须是cdecl函数,因此调用者可以负责,例如通过时复制它们并在返回时摧毁它们 .
任何见解将不胜感激 .
编辑:我仍然没有看到为什么建议的可变参数语义不起作用,但zneak的答案很好地说明了将编译器调整到它需要什么 - 所以我接受了它 . 最终,这可能是一些历史故障 .
3 回答
调用约定确实指定谁进行低级别堆栈跳舞,但它没有说明谁负责“高级”C簿记 . 至少在Windows上,按值接受对象的函数负责调用其析构函数,即使它不负责存储空间 . 例如,如果你构建这个:
你得到:
注意
main
如何构造两个Foo
对象,但只销毁一个;x
照顾另一个 . 如果对象作为vararg传递,那显然不会起作用 .编辑:将对象传递给具有可变参数的函数的另一个问题是,在当前形式中,无论调用约定如何,"right thing"都需要两个副本,而正常参数传递只需要一个副本 . 除非C扩展C变量函数,因为它可以传递和/或接受对象的引用(这种情况极不可能发生,因为C使用可变参数模板以类型安全的方式解决了同样的问题),调用者需要制作该对象的一个副本,
va_arg
仅允许被调用者获取该副本的副本 .微软的CL尝试在
va_arg
网站上使用一个按位拷贝和一个完整拷贝构造的那个按位拷贝,但它可能会产生令人讨厌的后果 . 考虑这个例子:在我的机器上,这打印"bar bar",即使它打印"foo bar"如果我有一个非可变参数函数,其第二个参数接受另一个
foo
副本 . 这是因为f
的按位副本发生在variadic
的调用站点的main
中,但只有在调用va_arg
时才会调用复制构造函数 . 在两者之间,a.setPtr
使原始的f.ptr
值无效,但是仍然存在于按位副本中,并且纯粹的巧合_strdup
返回相同的指针(尽管内部有一个新的字符串) . 同一代码的另一个结果可能是_strdup
崩溃 .请注意,此设计适用于POD类型;当构造函数和析构函数需要副作用时,它才会崩溃 .
调用约定和参数传递机制不一定支持非平凡构造和对象破坏的原始观点仍然存在:这正是这里发生的事情 .
编辑:答案最初说,建设和破坏行为是cdecl特有的;它不是 . (谢谢科迪!)
I'm recording this, because it's too big to be a comment, and it was reasonably time consuming to hunt this down, so no one else wastes time looking down this route.
该文本首先改为类似于2006-11-03发布的标准草案中的当前措辞 .
通过一些努力,我能够追溯到DR506的措辞 .
引用的论文(N1727)对该主题的说法很少:
然而,这并没有告诉我为什么它是这样的开始,这是你想知道的 . 我不可能将时钟转回到第一次写这种语言的时候,因为最早的免费提供的草案标准是从2005年起已经有了措辞你想知道,在此之前的所有标准要么需要认证,要么只是无内容 .
我想问题是/是否违反了类型安全 . 通常,将派生类对象传递给期望基类对象应该是安全的 . 如果基类对象是按值获取的,那么派生类对象将被简单地切片 . 如果它是由指针/引用 - 在编译期间正确调整派生类对象的指针/引用 . 这不适用于变量参数函数,其中输入类型的解释由代码而不是编译器执行 .
例:
EDIT
正如现在删除的注释所指出的,上例中的
A
,B
和D
实际上是POD类型 . 但是,我引起注意的问题与继承有关,虽然允许POD类型,但在大多数情况下涉及非POD类型 .