【问题标题】:Is Visual Studio buggy in printing the function address?Visual Studio 在打印函数地址时有问题吗?
【发布时间】:2014-08-28 03:00:26
【问题描述】:

采取以下测试用例:

#include <iostream>

void foo()
{}

int main()
{
   std::cout << &foo << std::endl;
}

GCC 4.1.2、GCC 4.8 和 GCC 4.9(C++03 和 C++11)在编译和编译时都是 give the following output

$ g++ main.cpp -o test && ./test
main.cpp: In function 'int main()':
main.cpp:8:23: warning: the address of 'void foo()' will always evaluate as 'true' [-Waddress]
   std::cout << &foo << std::endl;
                 ^
1

这可能是因为函数指针唯一可行的流插入是转换为bool(并且需要转换为void* 才能真正将地址放入流中)。

但是,Microsoft Visual Studio 2012 和 2013 改为输出指针地址。

哪组工具链符合要求?不符合项是否记录在任何地方?

【问题讨论】:

  • @LightnessRacesinOrbit 你已经回答了这个问题,不是吗?唯一可行的转换是bool (4.12),而 VS 错误地转换为 void*,这应该需要强制转换。
  • 没有从函数指针到void *的隐式转换。
  • @Praetorian:我没有在标准和/或文档中找到证明 VS 有扩展的证据。这就是我所追求的。证明! :)

标签: c++ visual-studio gcc language-lawyer


【解决方案1】:

如果禁用语言扩展(/Za 开关),可以使 MSVC 正常运行并执行从函数指针到 bool 的转换。如果您这样做,您的代码会产生以下警告(在 VS2013 上的 /W4

1>main.cpp(8): warning C4305: 'argument' : truncation from 'void (*)(void)' to 'std::_Bool'
1>main.cpp(8): warning C4800: 'void (*)(void)' : forcing value to bool 'true' or 'false' (performance warning)

输出是1


此行为是 Casts 部分下的 documented

C++ 编译器和 C 编译器都支持这些类型的非 ANSI 类型转换:
...
函数指针到数据指针的非 ANSI 类型转换

果然,下面这行编译只有/Za disabled

void *p = &foo;

禁用语言扩展会产生错误消息

1>main.cpp(8): error C2440: 'initializing' : cannot convert from 'void (*)(void)' to 'void *'
1>          There is no context in which this conversion is possible

【讨论】:

  • 嗯,这可能就够了。让我看看早上还会出现什么,否则我认为就是这样。谢谢
  • 很遗憾他们没有记录这种行为的非标准性质。我想知道为什么...
  • @LightnessRacesinOrbit 这些与rolled back VS2013 Update3 中的 mem-initializer 功能中的大括号或相等初始化器和列表初始化相同的人,因为它们是错误的,而不是修复它,然后在发行说明中留下了那个小花絮。我并不感到惊讶,这没有记录。
  • 哦,我从没说过我很惊讶!他们的文档通常是leaves something to be desired
【解决方案2】:

至少根据我对 N3337 的阅读,gcc 是正确的,而 MSVC 是不正确的(除非您禁用它的扩展)。

路径从标准的§4开始:

标准转换是具有内置含义的隐式转换。第 4 条列举了完整的此类转换。

因此,唯一存在的标准转换是第 4 节中列出的那些。但并非所有可能的标准转换都可以应用于每种情况。只能使用符合标准转换序列的那些。一个标准的转换顺序指定如下:

- 从以下集合进行零次或一次转换:左值到右值的转换、数组到指针的转换和函数到指针的转换。
— 从以下集合中进行零次或一次转换:整数提升、浮点提升、整数转换、浮点转换、浮点整数转换、指针转换、指向成员的指针转换和布尔转换。
— 零次或一次资格转换。

这里我们从一个指向函数的指针开始,所以第一个要点下的转换不能应用。我们不需要/关心资格转换,所以我们也不关心第三个要点。

pointer to function 转换为pointer to void 显然是指针转换。这些正好有三个品种。在 §4.10/1 中,我们从空指针常量开始进行指针转换(这显然不适用于此处)。 §4.10/2 涵盖从以下位置开始的转换:

“指向 cv T 的指针”类型的纯右值,其中 T 是对象类型 [...]

这显然也不适用于这里,因为函数不是对象。第三个选项是:

“指向 cv D 的指针”类型的纯右值,其中 D 是类类型 [...]

同样,函数不是类类型,因此也不能应用。

这让我们只有一个选择:直接从“指向函数的指针”到“布尔”的单次转换。那当然是布尔转换。 §4.12 说:

算术、无范围枚举、指针或指向成员类型的指针的纯右值可以转换为 bool 类型的纯右值。

因此,当且仅当 1)它是一个纯右值,并且 2)它是一个指针时,我们的值可以转换为布尔值。这似乎很明显,但如果我们想确认,我们可以查看 §5.3.1/2 和 5.3.1/3 中的地址运算符的定义:

以下每个一元运算符的结果都是纯右值。

满足第一个要求。

一元 & 运算符的结果是指向其操作数的指针。操作数应为左值或限定 ID。如果操作数是一个限定id,命名某个类C的一个非静态成员m,类型为T,则结果的类型为“指向类型T的类C成员的指针”,并且是一个指定C::m的纯右值。 否则,如果表达式的类型为 T,则结果类型为“指向 T”的指针,并且是纯右值,即指定对象 (1.7) 的地址或指向指定函数的指针。 [强调]

这显然满足了第二个要求——结果是一个指针。

既然满足了这些要求,就可以/将会发生转换。转换结果如下(回到§4.12):

将零值、空指针值或空成员指针值转换为false; 任何其他值都将转换为 true。

由于我们从一个指向实际函数的指针开始,我们不能有一个空指针。那只剩下一种可能性:“任何其他值都被转换为真。”

正如 gcc 的警告所说,转换的唯一可能结果是布尔值 true。默认情况下将打印为“1”,如果 boolalpha 已设置为 true,则打印为“true”。

【讨论】:

  • 我想我对此不是很清楚,但我想问一下 MSVS 提供的 operator&lt;&lt; 重载,以及该列表的合规性。
  • @LightnessRacesinOrbit:它使用的重载是采用void * 参数的重载。这种过载是必需的。这里的问题是,尽管重载必须存在,但不应使用它,因为从pointer to functionpointer to void 的转换不符合。
  • 哦,对了,所以 VS 的不符合性并不在于为函数指针提供非标准的 operator&lt;&lt;(事后看来,这将是相当愚蠢的,甚至是在 C++11 之前的版本中难以处理),而是在破坏指针转换规则?
  • @LightnessRacesinOrbit:正确。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-06-11
  • 2020-06-07
  • 1970-01-01
  • 1970-01-01
  • 2011-01-05
相关资源
最近更新 更多