C/C++内存分布与变量初始化顺序


关于入栈、出栈,栈顶栈底之类的分析见

函数调用的压栈出栈过程分析

下面继续分析C/C++的内存分布。



虽然0x10比一个变量需要的4个地址大了一些,但是0x10应该是规定的最小单位了。假如你要用的空间刚好是它的整数倍,其实是不浪费一分钱栈空间的,下边做一个数组,证明栈空间大小刚好是所有非静态变量占用空间的大小。

这里可以先无视hello的声明,它不在栈空间,后面会分析。

   x6       #include<stdio.h>                                       x
   x7       int main(){                                             x
   x9               static int hello;                               x 
B+>x11              int array[100] = {7,4,1};                       x
   x16              return 0;                                       x
   x17      } 

下边是汇编代码:比普通变量情况下复杂些,多用了一些寄存器,多了一些push、pop操作,还有rep指令。

   x0x8048394 <main>                push   %ebp                     x
   x0x8048395 <main+1>              mov    %esp,%ebp                x
   x0x8048397 <main+3>              push   %edi                     x
   x0x8048398 <main+4>              push   %ebx                     x
   x0x8048399 <main+5>              sub    $0x190,%esp              x
B+>x0x804839f <main+11>             lea    -0x198(%ebp),%ebx        x
   x0x80483a5 <main+17>             mov    $0x0,%eax                x
   x0x80483aa <main+22>             mov    $0x64,%edx               x100个变量(应该还有个标志类型大小是4的东西吧)
   x0x80483af <main+27>             mov    %ebx,%edi                x
   x0x80483b1 <main+29>             mov    %edx,%ecx                x
   x0x80483b3 <main+31>             rep stos %eax,%es:(%edi)        x
   x0x80483b5 <main+33>             movl   $0x7,-0x198(%ebp)        x初始化了三个数。
   x0x80483bf <main+43>             movl   $0x4,-0x194(%ebp)        x
   x0x80483c9 <main+53>             movl   $0x1,-0x190(%ebp)        x
   x0x80483d3 <main+63>             mov    $0x0,%eax                x
   x0x80483d8 <main+68>             add    $0x190,%esp 
   x0x80483de <main+74>     pop    %ebx                             x
   x0x80483df <main+75>     pop    %edi                             x
   x0x80483e0 <main+76>     pop    %ebp                             x
   x0x80483e1 <main+77>     ret                                     x
   x

下边是栈空间大小。两者相减,0x190,等于400bytes,刚好是100个int。

(gdb) print $esp
$1 = (void *) 0xbffff480
(gdb) print $ebp
$2 = (void *) 0xbffff618

我们都知道,”加了static就延长了变量的生命周期到程序结束“,或者还知道”加了static的变量地址不太一样“!

确实,地址变了,静态变量不在栈里。

int main(){
        static int hello;
        int top;
        return 0;
}
地址,top在栈中,而hello不在

(gdb) print &top
$3 = (int *) 0xbffff614
(gdb) print &hello
$4 = (int *) 0x8049614
栈大小是0x10
(gdb) print $esp
$3 = (void *) 0xbffff608
(gdb) print $ebp
$4 = (void *) 0xbffff618
去掉int型的声明,只留一个静态变量的声明,会发现一个有意思的现象
也就是说,连语句都被”优化“走了,不在main()函数里边执行了,不在main()函数里执行意味着什么?生命周期不光被向后延长到程序结束,也被向前延长了,它会在main之外,更早的地方被初始化。
那么如果我在静态变量声明语句之前,main()函数之内执行,究竟允不允许呢?可能语法上会做屏蔽,但是按运行顺序应该是可以的。
这句话换算成代码的话,是这样的:
#include<stdio.h>
int main(){
        hello = 4;
        static int hello;
        return 0;
}
连初学者都认为这样不可能,但是,如果我换一种写法呢?
#include<stdio.h>
static int hello;
int main(){
        hello = 4;
        return 0;
}
这样大家就都认可了吧!
但是我觉得这两个本质上是等价的, 只不过编译器在码农层面把这个操作给屏蔽了,可能太奇葩不便于理解。个人觉得是这样的!不用去试了,确实编译不过!!!!
换成可编译的版本运行一次:
   x0x8048394 <main>                push   %ebp                     x
   x0x8048395 <main+1>              mov    %esp,%ebp                x
B+>x0x8048397 <main+3>              movl   $0x4,0x8049614           x
   x0x80483a1 <main+13>             mov    $0x0,%eax                x
   x0x80483a6 <main+18>             pop    %ebp                     x
   x0x80483a7 <main+19>             ret                             x

main内声明肯定是看不到的,直接用了一下而已,如果没有那个赋值语句,main内部什么命理你个都看不到,直接就结束了。


我又想到另一个法子,因为编译后我可以看地址, 有时候(可能吧)不做”变量列表“(也就是所有变量类型和数量的综合)级的改动,其实地址都是差不多的,所以可以尝试先编译运行,看到地址后,再强行提取变量,C语言的强大!(C语言不是这样玩的好么-_-)
这是我的设想:
#include<stdio.h>
int main(){
        int *ptr = 0x8049614;
        printf("%d\n",*ptr);
        static int hello = 100;
        return 0;
}
print *ptr = 0(期望100)
没成功,但是还有希望,因为这次编译地址变了:
C/C++内存分布与变量初始化顺序_第1张图片

再来:
#include<stdio.h>
//static int hello = 100;
int main(){

        int *ptr = 0x8049578;
        printf("%d\n",*ptr);
        static int hello = 100;

        return 0;
}
不过讨厌的是又变了。。。。。。。这个(全局变量区?怎么叫来着?)地址分配很动态,不如栈的地址那么稳固。

一定要抓住它,这下试试664。

来个大写的骄傲,终于被我抓住了。
C/C++内存分布与变量初始化顺序_第2张图片
静态变量hello的声明和定义都在13行,但我在12行就提前并打印了出来。
来来来,再来个普通运行。



然并卵,现实中哪有人运行完了再去看结果,硬把地址写进去的,我也不知道这么玩能应用在哪。

这个例子只为证明:
1.静态变量的存储区域不在栈,并且也不像栈的地址规则那么稳定,但是还是能在一定程度上稳定住的,所以能抓住。
2.遇到那个“static”,这句声明加定义就和放在main外边没区别,这种代码的执行等于在程序正式执行之前就做完了,算初始化吧。
3.所以,无论静态变量声明在哪,它的生命周期都贯穿程序始终。
4.明明很早就声明定义了,之所以你不能直接这样提取静态变量的值,应该只是编译器设的规则,来避免一些不必要的人为错误吧。
5. static叫静态,静态静在哪?动态又动在哪?动态当然是动在你运行它才有结果,不运行就没有。栈虽然也会运行前就分配够空间,但是最起码变量的定义赋值是不会有的,而static,是一早就定义赋值完了,所以叫静态。(所以有充分理由怀疑编译器就是根据变量类型和数量,假设叫”变量列表“来分配栈空间的,谁叫我学渣没怎么学编译原理呢,只能先猜测一下)
6.不要模仿



扩展训练:
多加两层函数呢?


请容许我复习一下C++:
C++类静态成员是否会有所不同?是很早就初始化了,还是从某一个对象运行该函数开始?假如说也是带定义的形式,比如,static int i = 1;而不是static int i;
首先,第一种形式,ISO C++又不允许了(错误:ISO C++ 不允许在类内初始化非常量静)。
其次,又不允许使用A::i这种形式访问A的静态成员变量i。






关于main函数之前的那些汇编,我还不太懂,有空再摸索,想知道静态变量的初始化,各种类,还有虚函数表的初始化过程。






你可能感兴趣的:(C/C++内存分布与变量初始化顺序)