关键字const、volatile与指针的使用;汇编语言与寄存器状态的查看:
首先来看一个例子,为了防止是编译器自身优化的原因导致结果不一致性我们在这里禁止编译器优化(实际上这里与编译器优化无关):
例一(const与指针的使用导致的结果不一致性):
#include <iostream>
#include<stdio.h>
using namespace std;
int main()
{
#pragma optimize("", off) //禁止编译器优化下面一段代码
const int a = 10;
printf("%d\n",a);
int *p = (int*)&a;
*p = 100;
printf("%p\n%p\n",&a,p); //地址一样
printf("%d,%d\n",a,*p); //值不一样
return 0;
#pragma optimize("", on)
}
result:
原因:
int *p = (int*)&a;
*p = 100;
此处代码确实是将地址部分的内容加以改正了,但是a首先定义为const变量,所以编译器认为a在以后是不会改变的,因此在程序编译的时候,就把变量a开始的值10放在了寄存器中,以后访问的时候直接从寄存器中读取;故执行上述代码片段时虽然修改了内存中实际的值,但是却没有修改寄存器里的值,输出a的值的时候,还是从寄存器的读取的,而寄存器的值依然是10,所以导致了读取的值和实际值不相符。
例二(通过volatile实现结果的一致性):
#include <iostream>
#include<stdio.h>
using namespace std;
int main()
{
volatile const int a = 10; //看是否加入了限定字volatile,如果加上则结果值一样,反之则不一样
//变量如果加了 volatile 修饰,则会从内存重新装载内容,而不是直接从寄存器拷贝内容。
//const int a = 10;
printf("%d\n",a);
int *p = (int*)&a;
*p = 100;
printf("%p\n%p\n",&a,p); //地址一样
printf("%d,%d\n",a,*p); //值不一样
return 0;
}
加入volatile关键字之后,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据,而且读取的数据立刻被保存。因此使用volatile关键字避免了读取寄存器和读取内存的不一致性错误。
result:
通过上述两个例子也可以说明const并不是绝对安全的,const关键字并不能保证常量值被修改(可以通过指针直接修改地址值的方式等进行修改)。
当然上述例子我们可以在调试过程中通过观察汇编代码和寄存器状态进一步得出结论,有汇编语言基础的可以自行尝试一下。
例一(汇编代码):
#include <iostream>
#include<stdio.h>
using namespace std;
int main()
{
00007FF63C1C18F0 push rbp
00007FF63C1C18F2 push rdi
00007FF63C1C18F3 sub rsp,128h
00007FF63C1C18FA lea rbp,[rsp+20h]
00007FF63C1C18FF mov rdi,rsp
00007FF63C1C1902 mov ecx,4Ah
00007FF63C1C1907 mov eax,0CCCCCCCCh
00007FF63C1C190C rep stos dword ptr [rdi]
00007FF63C1C190E mov rax,qword ptr [__security_cookie (07FF63C1CC008h)]
00007FF63C1C1915 xor rax,rbp
00007FF63C1C1918 mov qword ptr [rbp+0F8h],rax
00007FF63C1C191F lea rcx,[__21349430_ConsoleApplication1@cpp (07FF63C1D1026h)]
00007FF63C1C1926 call __CheckForDebuggerJustMyCode (07FF63C1C108Ch)
//volatile const int a = 10; //看是否加入了限定字volatile,如果加上则结果值一样,反之则不一样
const int a = 10;
00007FF63C1C192B mov dword ptr [a],0Ah
printf("%d\n",a);
00007FF63C1C1932 mov edx,0Ah
00007FF63C1C1937 lea rcx,[string "%d\n" (07FF63C1C9CA4h)]
00007FF63C1C193E call printf (07FF63C1C11E0h)
int *p = (int*)&a;
00007FF63C1C1943 lea rax,[a]
00007FF63C1C1947 mov qword ptr [p],rax
*p = 100;
00007FF63C1C194B mov rax,qword ptr [p]
00007FF63C1C194F mov dword ptr [rax],64h
printf("%p\n%p\n",&a,p); //地址一样
00007FF63C1C1955 mov r8,qword ptr [p]
00007FF63C1C1959 lea rdx,[a]
00007FF63C1C195D lea rcx,[string "%p\n%p\n" (07FF63C1C9CA8h)]
00007FF63C1C1964 call printf (07FF63C1C11E0h)
printf("%d,%d\n",a,*p); //值不一样
00007FF63C1C1969 mov rax,qword ptr [p]
00007FF63C1C196D mov r8d,dword ptr [rax]
00007FF63C1C1970 mov edx,0Ah
00007FF63C1C1975 lea rcx,[string "%d,%d\n" (07FF63C1C9CB0h)]
00007FF63C1C197C call printf (07FF63C1C11E0h)
return 0;
00007FF63C1C1981 xor eax,eax
}
00007FF63C1C1983 mov edi,eax
00007FF63C1C1985 lea rcx,[rbp-20h]
}
00007FF63C1C1989 lea rdx,[__xt_z+1E0h (07FF63C1C9C80h)]
00007FF63C1C1990 call _RTC_CheckStackVars (07FF63C1C1343h)
00007FF63C1C1995 mov eax,edi
00007FF63C1C1997 mov rcx,qword ptr [rbp+0F8h]
00007FF63C1C199E xor rcx,rbp
00007FF63C1C19A1 call __security_check_cookie (07FF63C1C10DCh)
00007FF63C1C19A6 lea rsp,[rbp+108h]
00007FF63C1C19AD pop rdi
00007FF63C1C19AE pop rbp
00007FF63C1C19AF ret
例二(汇编代码):
#include <iostream>
#include<stdio.h>
using namespace std;
int main()
{
00007FF7D37E18F0 push rbp
00007FF7D37E18F2 push rdi
00007FF7D37E18F3 sub rsp,128h
00007FF7D37E18FA lea rbp,[rsp+20h]
00007FF7D37E18FF mov rdi,rsp
00007FF7D37E1902 mov ecx,4Ah
00007FF7D37E1907 mov eax,0CCCCCCCCh
00007FF7D37E190C rep stos dword ptr [rdi]
00007FF7D37E190E mov rax,qword ptr [__security_cookie (07FF7D37EC008h)]
00007FF7D37E1915 xor rax,rbp
00007FF7D37E1918 mov qword ptr [rbp+0F8h],rax
00007FF7D37E191F lea rcx,[__21349430_ConsoleApplication1@cpp (07FF7D37F1026h)]
00007FF7D37E1926 call __CheckForDebuggerJustMyCode (07FF7D37E108Ch)
volatile const int a = 10; //看是否加入了限定字volatile,如果加上则结果值一样,反之则不一样
00007FF7D37E192B mov dword ptr [a],0Ah
printf("%d\n",a);
00007FF7D37E1932 mov edx,dword ptr [a]
00007FF7D37E1935 lea rcx,[string "%d\n" (07FF7D37E9CA4h)]
00007FF7D37E193C call printf (07FF7D37E11E0h)
int *p = (int*)&a;
00007FF7D37E1941 lea rax,[a]
00007FF7D37E1945 mov qword ptr [p],rax
*p = 100;
00007FF7D37E1949 mov rax,qword ptr [p]
00007FF7D37E194D mov dword ptr [rax],64h
printf("%p\n%p\n",&a,p); //地址一样
00007FF7D37E1953 mov r8,qword ptr [p]
00007FF7D37E1957 lea rdx,[a]
00007FF7D37E195B lea rcx,[string "%p\n%p\n" (07FF7D37E9CA8h)]
00007FF7D37E1962 call printf (07FF7D37E11E0h)
printf("%d,%d\n",a,*p); //值不一样
00007FF7D37E1967 mov rax,qword ptr [p]
00007FF7D37E196B mov r8d,dword ptr [rax]
00007FF7D37E196E mov edx,dword ptr [a]
00007FF7D37E1971 lea rcx,[string "%d,%d\n" (07FF7D37E9CB0h)]
00007FF7D37E1978 call printf (07FF7D37E11E0h)
return 0;
00007FF7D37E197D xor eax,eax
}
00007FF7D37E197F mov edi,eax
00007FF7D37E1981 lea rcx,[rbp-20h]
}
00007FF7D37E1985 lea rdx,[__xt_z+1E0h (07FF7D37E9C80h)]
00007FF7D37E198C call _RTC_CheckStackVars (07FF7D37E1343h)
00007FF7D37E1991 mov eax,edi
00007FF7D37E1993 mov rcx,qword ptr [rbp+0F8h]
00007FF7D37E199A xor rcx,rbp
00007FF7D37E199D call __security_check_cookie (07FF7D37E10DCh)
00007FF7D37E19A2 lea rsp,[rbp+108h]
00007FF7D37E19A9 pop rdi
00007FF7D37E19AA pop rbp
00007FF7D37E19AB ret
VS调试生成汇编代码的方式:
先添加断点,然后进行调试,然后在“调试”->“窗口”->“反汇编”.这样才能看见反汇编选项。如果没有增加断点是看不见的。查看寄存器、内存等也是一样的。
当然也可以通过cmd命令行形式直接输出反汇编后的代码:
例(需要注意生成的.s文件需要注明详细地址,用txt文本文件格式打开即可):
C:\Users\Administrator>g++ -S C:\\Users\\Administrator\\Desktop\\testc++.cpp -o C:\\Users\\Administrator\\Desktop\\啦啦啦.s
当然我们也可以通过在线编译器来查看反汇编代码和输出结果:
推荐两个网址(实际在线编译器众多,这里只挑出有特色的的(并不一定是最好的,只有适合的才是最好的)):
C++ Shell
C++ Shell 系统使用的是 GCC 4.9.2,并带有 Boost 1.55。它具有语法高亮、错误提示等功能。此外,它还支持一些额外的选项,像 C++ 标准选择(C++98/C++11/C++14)、警告级别、优化级别、标准输入等。
虽然缺点相对较多,比如缺少智能提示、创建文件/项目、下载代码、自定义设置等功能,而且执行速度也较慢,但是警告级别、优化级别功能还是OK的。
Compiler Explorer
Compiler Explorer 是一个交互式编译器,左侧显示了可编辑的 C/C++、Go、Swift(以及更多)代码,右侧是编译代码后的程序集输出,比较适合用来查看汇编代码。它具有代码高亮、自定义设置、错误提示、汇编输出、保存、共享等功能。
它的缺点是没有智能提示,而且功能有点儿多,让人眼花缭乱!但查看汇编语言等功能很OK。