[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;
}