全局指针被优化器解析 - 但引用不会 - 为什么?

7
在VS2010优化器中,全局指针和全局引用之间的最大区别是什么?为什么引用没有被解析?请注意,本文不提供解释,只保留HTML标记。
typedef unsigned char byte_t;
typedef unsigned short word_t;

struct byte_reg_t
{
  byte_t low;
  byte_t high;
};

union word_reg_t
{
  word_t value;
  byte_reg_t part;
};

word_reg_t r16;

byte_t& low_ref = r16.part.low;
byte_t* const low_ptr = &r16.part.low;

#define SPLIT() _asm nop;

int main()
{
  low_ref = 4;
  SPLIT()

  byte_t a = r16.part.low;
  SPLIT()

  byte_t b = low_ref;
  SPLIT()

  byte_t c = *low_ptr;
  SPLIT()

  return a+b+c;
} 

使用发布模式编译并输出汇编代码会产生以下结果。
;byte_t a = r16.part.low;
mov cl, BYTE PTR ?r16@@3Tword_reg_t@@A

;byte_t b = low_ref;
mov edx, DWORD PTR ?low_ref@@3AAEA ; low_ref
mov dl, BYTE PTR [edx]

;byte_t c = *low_ptr;
mov al, BYTE PTR ?r16@@3Tword_reg_t@@A

未修改的反汇编

.text:00401000 _main           proc near               ; CODE XREF: __tmainCRTStartup+11D
.text:00401000                 mov     eax, ?low_ref@@3AAEA ; uchar & low_ref
.text:00401005                 mov     byte ptr [eax], 4
.text:00401008                 nop
.text:00401009                 mov     cl, ?r16@@3Tword_reg_t@@A ; word_reg_t r16
.text:0040100F                 nop
.text:00401010                 mov     edx, ?low_ref@@3AAEA ; uchar & low_ref
.text:00401016                 mov     dl, [edx]
.text:00401018                 nop
.text:00401019                 mov     al, ?r16@@3Tword_reg_t@@A ; word_reg_t r16
.text:0040101E                 nop
.text:0040101F                 movzx   eax, al
.text:00401022                 movzx   edx, dl
.text:00401025                 movzx   ecx, cl
.text:00401028                 add     eax, edx
.text:0040102A                 add     eax, ecx
.text:0040102C                 retn
.text:0040102C _main           endp

.data:00403374 ?r16@@3Tword_reg_t@@A db ?              ; DATA XREF: _main+9
.data:00403374                                         ; _main+19
.data:00403375                 align 4

.data:00403018 ; unsigned char & low_ref
.data:00403018 ?low_ref@@3AAEA dd offset ?r16@@3Tword_reg_t@@A ; DATA XREF: _main
.data:00403018                                         ; _main+10
.data:00403018                                         ; word_reg_t r16

我测试了几个变量(从函数返回等)- 如果low_ref在使用中,则无法解决。

  • 优化器很蠢吗?
  • 这是优化的一个不常见情况吗?
  • 一些C/C++标准限制?

更新

这似乎是一种不常见的优化情况 - 感谢Michael Burr

如果引用在函数作用域内或在类或结构体中实例化于函数作用域之内,则可以工作 (但仍然奇怪,优化器可以解析指向const指针而不能解析引用 - 它们是完全相同的)

更新2

更奇怪的是,如果您从byte_t切换到int,则两种解析都可以 - const指针和引用

  • 全局ptr const到全局byte_t变量:已解决
  • 全局ptr const到全局int变量:已解决
  • 全局引用到全局byte_t变量:未解决
  • 全局引用到全局int变量:已解决
  • 全局引用到本地byte_t变量:已解决
  • 全局引用到本地int变量:已解决

因此,在const指针和引用的优化器中有一点差异,引用作用域......以及所参考的类型......有时会有所不同 :)

更新3

更简单的测试代码 - 使用VS2010和clang 3.1进行检查

typedef unsigned char byte_t;
typedef unsigned int dword_t;

//for msvc
#define SPLIT() _asm nop _asm nop;
//for clang
//#define SPLIT() asm("nop"); asm("nop");

byte_t byte;
dword_t dword;

byte_t& global_ref_byte = byte;
dword_t& global_ref_dword = dword;

byte_t* const global_ptrc_byte = &byte;
dword_t* const global_ptrc_dword = &dword;

int main(int argc, char** argv)
{
  byte_t& local_ref_byte = byte;
  dword_t& local_ref_dword = dword;

  dword_t random = (dword_t)argv;

  byte = (byte_t)random;
  dword = (dword_t)random;
  SPLIT()

  byte_t a = global_ref_byte;
  SPLIT()

  dword_t b = global_ref_dword;
  SPLIT()

  byte_t c = *global_ptrc_byte;
  SPLIT()

  dword_t d = *global_ptrc_dword;
  SPLIT()

  byte_t e = local_ref_byte;
  SPLIT()

  dword_t f = local_ref_dword;
  SPLIT()

  dword_t result = a+b+c+d+e+f;

  return result;
}

VS2010反汇编

.text:00401000 ; int __cdecl main(int argc, const char **argv, const char **envp)
.text:00401000 _main           proc near               ; CODE XREF: ___tmainCRTStartup+11D
.text:00401000
.text:00401000 argc            = dword ptr  8
.text:00401000 argv            = dword ptr  0Ch
.text:00401000 envp            = dword ptr  10h
.text:00401000
.text:00401000                 push    ebp
.text:00401001                 mov     ebp, esp
.text:00401003                 mov     eax, [ebp+argv]
.text:00401006                 push    ebx
.text:00401007                 push    esi
.text:00401008                 push    edi
.text:00401009                 mov     byte_403374, al
.text:0040100E                 mov     dword_403378, eax
.text:00401013                 nop
.text:00401014                 nop
.text:00401015                 mov     eax, off_40301C
.text:0040101A                 mov     al, [eax]
.text:0040101C                 nop
.text:0040101D                 nop
.text:0040101E                 mov     ecx, dword_403378
.text:00401024                 nop
.text:00401025                 nop
.text:00401026                 mov     dl, byte_403374
.text:0040102C                 nop
.text:0040102D                 nop
.text:0040102E                 mov     esi, dword_403378
.text:00401034                 nop
.text:00401035                 nop
.text:00401036                 mov     bl, byte_403374
.text:0040103C                 nop
.text:0040103D                 nop
.text:0040103E                 mov     edi, dword_403378
.text:00401044                 nop
.text:00401045                 nop
.text:00401046                 movzx   edx, dl
.text:00401049                 movzx   ebx, bl
.text:0040104C                 add     edx, edi
.text:0040104E                 movzx   eax, al
.text:00401051                 add     edx, ebx
.text:00401053                 add     eax, edx
.text:00401055                 pop     edi
.text:00401056                 add     eax, esi
.text:00401058                 pop     esi
.text:00401059                 add     eax, ecx
.text:0040105B                 pop     ebx
.text:0040105C                 pop     ebp
.text:0040105D                 retn
.text:0040105D _main           endp

clang 3.1反汇编

.text:004012E0 sub_4012E0      proc near               ; CODE XREF: sub_401020+91
.text:004012E0
.text:004012E0 arg_4           = dword ptr  0Ch
.text:004012E0
.text:004012E0                 push    ebp
.text:004012E1                 mov     ebp, esp
.text:004012E3                 call    sub_4014F0
.text:004012E8                 mov     eax, [ebp+arg_4]
.text:004012EB                 mov     byte_402000, al
.text:004012F0                 mov     dword_402004, eax
.text:004012F5                 nop
.text:004012F6                 nop
.text:004012F7                 movzx   eax, byte_402000
.text:004012FE                 nop
.text:004012FF                 nop
.text:00401300                 add     eax, dword_402004
.text:00401306                 nop
.text:00401307                 nop
.text:00401308                 movzx   ecx, byte_402000
.text:0040130F                 add     ecx, eax
.text:00401311                 nop
.text:00401312                 nop
.text:00401313                 add     ecx, dword_402004
.text:00401319                 nop
.text:0040131A                 nop
.text:0040131B                 movzx   eax, byte_402000
.text:00401322                 add     eax, ecx
.text:00401324                 nop
.text:00401325                 nop
.text:00401326                 add     eax, dword_402004
.text:0040132C                 nop
.text:0040132D                 nop
.text:0040132E                 pop     ebp
.text:0040132F                 retn
.text:0040132F sub_4012E0      endp

如果没有空操作指令,两个优化器都可以生成更好的代码,但Clang仍然更好。

VS2010由于未解决的字节引用问题,需要更多的代码。

.text:00401003                 mov     eax, [ebp+argv]
.text:00401006                 movzx   ecx, al
.text:00401009                 lea     edx, [eax+eax*2]
.text:0040100C                 mov     byte_403374, al
.text:00401011                 mov     dword_403378, eax
.text:00401016                 lea     eax, [edx+ecx*2]
.text:00401019                 mov     ecx, off_40301C
.text:0040101F                 movzx   edx, byte ptr [ecx]
.text:00401022                 add     eax, edx

clang 3.1:

.text:004012E8                 mov     eax, [ebp+arg_4]
.text:004012EB                 mov     byte_402000, al
.text:004012F0                 mov     dword_402004, eax
.text:004012F5                 movzx   ecx, al
.text:004012F8                 add     ecx, eax
.text:004012FA                 lea     eax, [ecx+ecx*2]

低指针的地址是如何最终出现在PTR中的?也许您可以展示完整的反汇编代码,而不仅仅是选定的行? - RedX
你说的“最终在PTR中结束”是什么意思?变量“?r16@@3Tword_reg_t@@A”是全局变量“word_reg_t r16”,在反汇编中没有其他可见的代码,这是nops之间唯一的代码。 - llm
哦,抱歉,我以为看到那里有一个分号。现在没问题了,更有意义。 - RedX
1个回答

5
这是我认为正在发生的事情。该引用被视为一个非常量全局指针。如果您从low_ptr声明中删除const,可以看到这一点。
如果将引用移动到函数的本地,则可以看到编译器能够优化对其的访问而没有问题。
我猜由于全局引用相当少见(我承认这只是我杜撰的“统计数据”),所以很少有人会投入精力来优化它们。

它适用于int作为基本类型的情况 :) - 如果只使用byte_t,则仅解析ptr const - llm

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接