[C++]What the hell with "char A::*" ?
事情是这样的,一位群友在某C++学习群发了这么一个顺利编译通过的东西:
我:
后面有群友回复觉得这是编译器放过简单的错误,但我不是这么觉得
网上能力有限没找到相关资料,于是花了大约一小时(作为热身)研究了一下这个特性
- 首先编译通过在G++10.2.1和MSVC1930上是没问题的,但是细节上有些地方不同,比如G++目前禁止下面这种写法,而MSVC就不行:
extern class A;
char A::*ebytes;
- 其次通过typeid得知在编译器里这个变量是个指针,类型是
char A::*
推测可以解释成指向char A::
类型的变量的一个指针,这个char A::
又是类A作用域里的一个char类型的变量
- 最后经过各种花里胡哨的语句进行调试该变量在赋值、作用域等方面的特性
(1)它不属于类A的作用域而属于它被声明的位置。这很好理解,指向这个作用域里面的一个东西不代表就属于这个作用域
(2)正常使用的类A的静态和自动变量或当前上下文的全局变量和自动变量等都无法赋值给这个变量。这个是因为A的成员变量的类型正常使用是不带作用域限定A::
,
而这个变量必须是指向类型显式声明有A::
的变量。
以用A的静态变量赋值为例:表达式不带A::
,该符号在当前上下文找不到;表达式带A::
,表达式的类型不带A::
(3)赋值还是可以赋值的,但是一般因为类型无法转换不会出现这种情况。
用A的自动变量的identifier前面加作用域限定符“A::”就可以得到一个莫名其妙的“A::”类型
结论:
(1)::*
这种写法合法,而且大概就是群友理解的“类成员指针”,但是在一般情况下几乎用不到,因为在类外应该获取不到类型带<class-name>::
的右值表达式。
(2)至于能通过,是因为被当作了声明并定义在当前上下文的一个稍微有点特殊的指针而已,可能算是个缺陷或者未定义行为。
(3)至于能赋值的情况,推测是:编译器虽然在当前版本不允许类内静态变量与自动变量重名,但是不保证以前版本没有允许,或者实现编译器的人根本没有考虑这种情况在表达式中出现;
而在表达式分析时,这个<class-name>::<&>identifier
的freak被保守地当作已经声明了但不知道在哪定义的重名的静态变量,而类型暂时用了已定义的自动变量的。
从输出结果也可得知,这个<class-name>::identifier
和类内自动变量identifier
的地址根本不一样:在本人的环境编译链接后,在MSVC两者有一个offset,在g++前者的地址直接是0x00000000。可以合理推测前者就是编译器符号表里“暂时假设它有”的一个东西。
一个小时乱搞,半小时写blog,纯粹pedantic的C++律师问题但是又不够pro,基本胡言乱语比较偏主观,疏忽和不严谨的地方敬请批评指正,如果有关于这方面的规范文档欢迎分享。
附:
在Debian g++ 10.2.1下编译通过的测试程序代码:
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <iostream>
class A {
public:
static char* bytes;
static char* cytes;
static char dyte;
char eyte;
A();
};
char A::* ebytes;
A::A() {
eyte = 'A';
ebytes = &A::eyte;
printf("not static var addr: %08x %08x\n", &eyte, &A::eyte);
}
char buf[500];
char A::dyte;
//char *A::bytes = buf;
int A::* cytes;
// std::string A::* bytes;
// char A::* bytes;
char* A::bytes = buf;
// char *A::cytes = buf;
int main()
{
//printf("%s\n", typeid(ebytes).name());
strcpy(buf, "123456");
//ebytes = bytes;
A a{};
printf("%08x\n%08x\n%08x\n", ebytes, A::bytes, buf);
printf("%s\n", ebytes);
return 0;
}
在Windows MSVC1930 下编译通过的测试程序代码:
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <iostream>
extern class A;
char A::* ebytes;
class A {
public:
static char* bytes;
static char* cytes;
static char dyte;
// char bytes;
char eyte;
A() {
eyte = 'A';
ebytes = &A::eyte;
printf("not static var addr: %08x %08x\n", &eyte, &A::eyte);
}
};
char buf[500];
char A::dyte;
//char *A::bytes = buf;
int A::* cytes;
// std::string A::* bytes;
// char A::* bytes;
char* A::bytes = buf;
// char *A::cytes = buf;
int main()
{
printf("%s\n", typeid(ebytes).name());
strcpy_s(buf, "123456");
//ebytes = bytes;
A a{};
printf("%08x\n%08x\n%08x", ebytes, A::bytes, buf);
printf("%s\n", ebytes);
return 0;
}