天天看点

printf 地址_变量名和变量地址

        操作系统是如何分配内存的

        在分析变量名和变量地址之前,要说一下,操作系统是如何管理内存的。首先操作系统肯定不是踏踏实实管理内存的,因为踏踏实实管理,内存肯定是不够用的。打开任务管理器,可以看到此时的进程有 91 个,如果 1 个进程占 4G ,就是 364G 内存,少算很多,50G 的内存,去哪里找?

printf 地址_变量名和变量地址

        大体说一下内存是如何分配的。内存有长达 4 个 G 的空间,从 16 进制的 00000000 - ffffffff ,正常情况,不考虑别的编译选项的情况,从 80000000 开始,下面是操作系统所需要的数据和代码。上面是属于用户空间,但是 Windows 还要掐头去尾 64K ,前 64K 做无效指针和无效句柄的检查,后 64K 做内核的交互,剩下的才是程序员可以玩的空间。在剩下的空间中又分为四个区,排名不分先后,分别是:代码区,数据区,堆区,栈区。数据区又分为:已初始化数据区,未初始化数据区。已初始化数据区又分为:只读数据区,可读写数据区。大体内存分配情况如下图:

printf 地址_变量名和变量地址

        为了验证上面所说的,用 WinHex 选择打开 RAM,这里打开了 lantern 。可以看到起始地址是从 00010000 开始的

printf 地址_变量名和变量地址

         最后面的地址是 7FFE6FF0 ,没有到达,也永远到不了 7fff0000 之后,成功验证了前面所说的,前 64K 做无效指针和无效句柄的检查,后 64K 做内核的交互

printf 地址_变量名和变量地址

     在滑动鼠标的过程中发现,从 00010FA0,00010FB0,00010FC0,00010FD0,00010FE0,00010FF0 内存地址都是紧挨着了,但是 00010FF0 之后 应该是 00011000 ,而 WinHex 中却显示 00020000,这又是为什么呢

printf 地址_变量名和变量地址

        这就是俗称的缺页,因为没有用这块内存地址,所以不给分配。这就是操作系统的按需分配,操作系统在模拟一个人在高空楼中走步的环境,如果人脚下有地板那么是不会掉下去的,但是这个环境,只保证人的脚下有地板,其他地方是悬空的,脚在哪里,地板就在哪里,当往前走的时候,原来的地板就消失了,后来的地板又凭空产生。如果这个时候把眼睛蒙上,会以为有一块完整的地板在地面上,不会觉得地板是不完整的。所以在使用电脑的时候,会以为需要的内存无处不在。

        变量名和变量地址

        从上面的截图中可以看到,数据是放在内存中的,下面用 C 语言写一个简单的程序,命名为:variableNameAndAddress.c

        程序功能为:输入一个数字,然后加 4 ,最后输出结果

#include int main(int argc, char argv[], char *envp[]){  int n;  scanf("%d", &n);  printf("%d\r\n", n + 4);  return 0;}
           

        如果是低级语言,上面的程序,就要写内存地址。假设 n 的地址是 0x18ff44 ,先读取 0x18ff44 的内容,然后和 4 相加,最后写到 printf  的参数部分去。所以在低级语言时,每个变量都要记住它的地址,烦得很。所以,编译器给地址命名解决了这个问题,编译器做了若干个表,把某个内存地址和某个文本的符号名称做了对应关系表,当引用某一个文本名称的时候,编译器就会查这个表,产生操作某个地址的代码。例如把 0x18ff44 命名为 n ,就不需要记住 0x18ff44 ,而只需要记住 n 就可以了。

        变量名就是对地址命名的,所以一定要做规范,并且见名知意。通常变量要初始化,不初始化是不规范的,因为初值是上次使用这段内存残留的值。下面将程序进行一下小改动,定义一个变量 n ,然后打印 n 的地址,向 n 的地址里输入一个数字,然后打印 n

#include #include int main(int argc, char argv[], char *envp[]){  int n;  printf("%p\r\n", &n);  scanf("%d", &n);  printf("%d\r\n", n)  system("pause");  return 0;}
           

        将程序编译链接执行,打印出 n 的地址为 0019FF2C

printf 地址_变量名和变量地址

        打开 WinHex ,定位到 variableNameAndAddress ,然后转到虚拟地址 0019FF2C 去,可以看到里面的值为:E0904000

printf 地址_变量名和变量地址

        当输入 999 后

printf 地址_变量名和变量地址

        999 的 16 进制是 3E7 ,可以看到 0019FF2C 地址里面的值变成了 E7030000。那为什么 000003E7,WinHex 中显示的却是 E7030000 呢?这就涉及了字节存储顺序的两种方式,分别为:小尾方式和大尾方式。小尾方式是:高数据位对高地址,低数据位对低地址。大尾方式:高数据位对低地址,低数据为对高地址。计算机有的选择小尾方式,有的选择大尾方式。可以看到 WinHex 中从上大小,从左往右地址由低变高。英特尔是小尾方式,所以 000003E7 ,从左往右,00 对应高地址 0019FF2F, 00 对应 0019FF2E ,03 对应 0019FF2D ,E7 对应低地址 0019FF2C。所以计算机通讯不是先塞数据进去,首先是先约好数据的存放方式。

printf 地址_变量名和变量地址

        刚学 C 的时候经常犯的一个错误,就是 scanf 忘记写了取地址符 &  

#include #include int main(int argc, char argv[], char *envp[]){  int n;  printf("%p\r\n", &n);  scanf("%d", n);  printf("%d\r\n", n);  system("pause");  return 0;}
           

        当时这么写的时候,老师只说不要这么写,这么写语法是不对的。可是内部原理真的是这样吗?首先编译一定会通过的,scanf 是多参函数,是没条件做参数类型检查的。将上面的程序编译运行。打印出 n 的地址仍然为 0019FF2C

printf 地址_变量名和变量地址

        打开 WinHex ,发现在没给值之前,地址 0019FF2C 里面的内容是 E0904000

printf 地址_变量名和变量地址

        这时输入 999 后,输出的却是 4231392 。这是为什么呢?打开 WinHex ,定位到 0019FF2C ,发现里面的值是 E0904000 ,按小尾方式排序,原来的 16 进制就是 004090E0 ,对应的 10 进制就是 4231392

printf 地址_变量名和变量地址

        而 999 写到了 004090E0 这个地址里面去了,可以看到 004090E0 这个地址里面的值为 16 进制的 000003E7 ,也就是 10 进制的 999

printf 地址_变量名和变量地址
scanf("%d", &n);  是往 n 对应的地址里面写入了 999
           
而 scanf("%d", n);  是往 n 对应的地址的值为地址写入了 999
           

        计算机是一个很笨的东西,就像水果榨汁机一样,扔苹果进去,出来的就是水果汁;扔柠檬进去,出来的就是柠檬汁;扔一只手进去,出来的就是事故现场。把上面的程序稍作修改,让 n 的初值为 6

#include #include int main(int argc, char argv[], char *envp[]){  int n = 6;  printf("%p\r\n", &n);  scanf("%d", n);  printf("%d\r\n", n);  system("pause");  return 0;}
           

        0019FF2C 地址里面的值是 6

printf 地址_变量名和变量地址

        把 999 写入为 6 的地址,可想而知,肯定会报错的,因为前 64K 做无效指针和无效句柄的检查, 是不允许使用的。

printf 地址_变量名和变量地址

        所以这种 scanf 不写取地址,就好比高空弹鼻屎,可能会扔到人身上惹出祸来(直接把值写进了让程序崩溃的地址);可能会扔到地上什么事都没有(把值写进了其他地址,但是一直没有被调用);可能会扔到地上,某天被人踩了一脚惹出事来(把值写进了其他地址,并且有一天被其他程序调用,但是篡改了其中的值,而引发了麻烦)。为了防止这种麻烦,一定要在定义变量的时候赋初值。

        输入 n 修改 m 的值

        先说一下初学用 VC 的好处,在默认的情况下它是一个固定靶,它的内存中的代码段,数据段,堆区,栈区是相对固定的,而 2019 内部结构还是一样,但是它的位置是随机的。

        再将程序稍作改动,让 n 的值为 0019ff2c ,打印 n 的地址,然后输入 n ,最后输出 n 和 m 的 16 进制值.

#include #include int main(int argc, char argv[], char *envp[]){  int n = 0x19ff2c;  int m = 9;      scanf("%d", n);  printf("%08x, %08x\r\n", n, m);  system("pause");  return 0;}
           

        打印出 n 的地址是 0019FF28 ,而输入的 n 是 999。输出结果 n 的值为 0019ff2c ,而 m 的值确实 999 的 16 进制,这又是为什么呢?

printf 地址_变量名和变量地址

        打印出 n 的地址是 0019FF28 。打印 n 就是去 n 的地址里面取值,n 的地址是 0019FF28,里面存的值就是 0019FF2C ,所以打印出来也是 0019FF2C 。

        打印 m 就是去 m 的地址里面取值,因为内存是连续分配的,n 的地址是 0019FF28 ,所以m 的地址就是 0019FF2C 。而 scanf("%d", n) 的执行过程是:输入 999 后,就是把 999 放入了 n 对应的地址(0019FF28)里面的值(0019FF2C(也就是 m 对应的地址))为地址的地方去了,所以输出 m 的结果就是 999 的 16 进制 000003e7

printf 地址_变量名和变量地址