您的位置:首页 > 编程语言 > C语言/C++

读c++ primer有感----局部和全局变量,extern,static

2017-04-24 17:43 323 查看
一、全局和局部的可见范围

1.首先全局是相对的,局部就是函数内或括号内可用,全局有文件内可见、库内可见、链接时可见这些“全局”的;    

局部的例子:

#include<iostream>

int main(){

    int i = 100;

    for(int i = 0;i < 10;i++){

        std::cout<<i<<std::endl;

    }

    std::cout<<i<<std::endl;

}

两个i都是局部的。

文件内可见全局,用static限定:

try.cpp:

nclude<iostream>

int i = 100;

int main(){

    std::cout<<i<<std::endl;

}

try1.cpp

static int i = 200;

g++ try.cpp try1.cpp

输出100,因为try1.cpp内部的i是文件内可见。

如果try1.cpp改成:

int i = 200;

那就链接失败了,因为try和try1的“全局”变量i都是链接时可见,std::cout<<i<<std::endl找i的符号时发现了两个,冲突了。

库内可见,用编译选项-fvisibility和__attribute ((visibility("XXX")))来实现,如:

###########以下是实验中的插曲,可以忽略##############

try1.cpp

static int i = 200;

try2.cpp

#include<iostream>

extern int i;

void printI(){

    std::cout<<i<<std::endl;

}

try.cpp

#include<iostream>

void printI();

int i = 100;

int main(){

    printI();

}

g++ -fpic -c try1.cpp try2.cpp 把try1和try2按相对位置方式编译;

g++ -shared try1.o try2.o -o libtry.so 打包成动态链接库;

g++ try.cpp -ltry -L/home/liu3/blogs/globalweak main函数和libtry了解下对方;

export LD_LIBRARY_PATH=/home/liu3/blogs/globalweak 动态链接库查找路径添加本文件;

./a.out

g++ try.cpp try1.o

输出100,仿佛说明try1的i对try2是不可见的。

try1.cpp改成:

int i = 200;

好吧,输出100,动态链接没有冲突。

nm libtry.so 和nm a.out

00002028 D i

0804a02c D i

尽管动态链接符号表都有i,但是根据https://www.akkadia.org/drepper/dsohowto.pdf文中所述,动态链接是允许重复符号的,在查找符号时先遇到谁就是谁。

###########插曲结束##############

try.cpp:

#include<iostream>

void printGlobal();

extern int global;

int main(){

    printGlobal();

    std::cout<<global<<std::endl;

}

try1.cpp

int global = 200;

try2.cpp:

#include<iostream>

extern int global;

void printGlobal(){

    std::cout<<i<<std::endl;

}

g++ -fpic -c try1.cpp try2.cpp

g++ -shared -o libtry.so try1.o try2.o

g++ try.cpp -ltry -L/home/liu3/blogs/globalweak

./a.out

输出 200 200,大家都很happy,因为global是库内库外全局可见的。

try1.cpp改成

static int global = 200;就都找不到了,try先会报global找不到,即使注释输出global那一行后,libtry也会报try2找不到global的定义。

try1.cpp改成:

int __attribute__ ((visibility("hidden"))) global = 200;

会出现 try.cpp:(.text+0xf):对‘global’未定义的引用,注释输出global行以后就可以了,可见此时global只对库内可见。

全局可见的比较显然了,就不举例子了。

最后再加一个weak的例子:

try.cpp:

#include<iostream>

void printGlobal();

int global;

int main(){

    std::cout<<global<<std::endl;

}

try1.cpp:

int global = 200;

g++ try1.cpp try.cpp

两个文件中的全局global静态编译会符号冲突。

把try.cpp中相关行改一下:

int __attribute__((weak)) global;

就可以了,输出200。此时try中的global被当成“备胎”,其他处有全局的定义就被忽略了。

总结:局部变量的可见范围就是函数内,全局变量有文件内、库内、链接时,也有可被作为“备胎”的weak全局变量。

二、全局和局部的生命周期和内存分布

局部变量地址在栈内,也就是说,对应的栈地址被pop后该地址的读取行为就为定义了。

看个栈分配地址的基本例子:

int main(){

    int local[333];

}

g++ -S 输出汇编代码:

    subl    $1344, %esp //栈顶减1344,就是栈“生长”了1344B

333 * 4 = 1332;1332 / 16 = 83;84 * 16 = 1344;

“开辟”1344的栈空间估计是为了和16B的某些东西(实在不清楚是啥)对齐吧。开辟两个字打引号是因为,显而易见,分配局部变量的栈空间代价极小,仅仅是栈顶寄存器sp作个减法。和我们想象中的类似动态分配内存malloc的可能地查可用地址空间,找到最优的地址段,再修改某个表说明该段地址已被占用等等一系列操作比起来,实在是相当简单。

再看个收回栈地址的例子:

void haha(){

    int local[333];

}

int main(){

    haha();

}

main内:

    call    _Z4hahav

    movl    $0, %eax

    popl    %ebp

haha内:

    subl    $1344, %esp

main 调用(call)haha后,haha减栈顶1344“分配”的空间,完事控制权返回main后,main弹出栈顶popl赋值给ebp栈基址就成了。那这个1344的命运为啥是未定义的,单就此例,如果栈往下的空间没有被程序明确告诉操作系统不要了(这种交易具体如何进行还不太清楚),那么别的程序还动不了它,只是对本程序说这片地址已经成了无主之地,要用时直接减栈顶就“征用”了。

如果说局部变量的地址不固定是相对于栈的,并且生命周期也看啥时候popl %ebp。那么全局变量都是有固定地址的,并且该地址随程序结束才消失。

几个例子:

try.cpp:

extern int global;

int main(){

    std::cout<<global<<std::endl;

}

try1.cpp

int global = 100;

g++ try.cpp try1.cpp

realelf -a a.out得到

59: 0804a034     4 OBJECT  GLOBAL DEFAULT   24 global

[24] .data             PROGBITS        0804a02c 00102c 00000c 00  WA  0   0  4

可见global放到了数据段.data段内。

改一下try.cpp:

const int global = 1000;

int main(){

    std::cout<<global<<std::endl;

}

080487a0     4 OBJECT  LOCAL  DEFAULT   15 _ZL6global

[15] .rodata           PROGBITS        08048798 000798 00000c 00   A  0   0  4

global被放到了只读数据段.rodata,并且还改名了。

int global[10000] = {100};

int main(){

}

57: 0804a040 40000 OBJECT  GLOBAL DEFAULT   24 global

[24] .data             PROGBITS        0804a020 001020 009c60 00  WA  0   0 32

还是.data段。

global不初始化

int global[10000];

0804a060 40000 OBJECT  GLOBAL DEFAULT   25 global

[25] .bss              NOBITS          0804a040 001028 009c64 00  WA  0   0 32

现在到了.bss段。

看个weak的

try.cpp

int __attribute__((weak)) global = 1000;

int main{

}

12: 00000000     4 OBJECT  WEAK   DEFAULT    3 global

[ 3] .data             PROGBITS        00000000 0000c4 000004 00  WA  0   0  4

先编译不链接此时被放到了.data。

链接后

12: 00000000     4 OBJECT  WEAK   DEFAULT    3 global

[ 3] .data             PROGBITS        00000000 0000c4 000004 00  WA  0   0  4

还是放那儿。

用objdump -D分别看try.o和a.out

Disassembly of section .data:

00000000 <global>:

   0:   e8                      .byte 0xe8

   1:   03 00                   add    (%eax),%eax



0804a038 <global>:

 804a038:   d0 07                   rolb   (%edi)

后面的汇编指令大概是没用的(好象是objdump把数据当成指令了,03被当成了add),1000是3E8,2000是7D0,数字对得上。

总之,局部变量地址在栈内,生命周期是暂时的。全局变量无论可见范围怎么样,也无论是放.rodata .data .bss或者编译和链接后值是否一致,他们都有在整个程序执行过程中不变的地址。

三、我的总结

1.局部就是指地址空间在栈内的变量;

int main(){

    int i = 333;

    {

        int i = 666;

    }

}

    subl    $16, %esp

    movl    $333, -8(%ebp)

    movl    $666, -4(%ebp)

都是栈。

2.括号外的全局变量,就是没有被任何函数、类域包起来的变量。static表示本文件内全局可见,attribute visibility为hidden的库内可见,否则链接时可见。它们都有随程序生命周期的固定地址;

3.extern

try.cpp

extern int global;

int main(){

    std::cout<<global<<std::endl;

}

try1.cpp

int global = 2000;

try1.s

    .globl  global

    .data

    .align 4

    .type   global, @object

    .size   global, 4

global:

    .long   2000

try.s

movl    global, %eax

try里面就是一个叫global的符号其他啥信息也没有,try1提供global符号的所有信息。

nm 分别去看try.o、try1.o、a.out得到:

U global

00000000 D global

0804a034 D global

在try中没有定义的global和try1中定义到.data段的global链接后被放到
b16a
了可执行程序的.data里面。

可见,extern的变量就是全局的,在函数内部声明也一样,因为extern意味这变量不在本文件被定义,所以必须去”外面找,这个外面内部隐藏了全局的意思。

4.函数内static变量,以及static关键字

int getGlobal(){

    static int global = 1000;

    return global;

}

int main(){

    std::cout<<getGlobal()<<std::endl;

}

汇编部分:

    .type   _ZZ9getGlobalvE6global, @object

    .size   _ZZ9getGlobalvE6global, 4

_ZZ9getGlobalvE6global:

    .long   1000

nm信息:

0804a034 d _ZZ9getGlobalvE6global

readelf信息:

38: 0804a034     4 OBJECT  LOCAL  DEFAULT   24 _ZZ9getGlobalvE6global

[24] .data             PROGBITS        0804a02c 00102c 00000c 00  WA  0   0  4

待遇和初始化的全局变量类似放在了.data数据段内。

绑定方式是LOCAL,说明链接时其他文件看不到这个符号。嗯,一个生命周期为整个程序的变量,从其他文件直接用符号链接不到,没关系,只要能获得地址就成。

try.cpp:

#include<iostream>

int *getGlobal(){

    static int global = 1000;

    return &global;

}

void changeGlobal();

int main(){

    changeGlobal();

    std::cout<<*getGlobal()<<std::endl;

}

try1.cpp

int *getGlobal();

void changeGlobal(){

    int *pi = getGlobal();

    *pi = 2000;

}

结果是2000。
所以不要被static关键字吓到,一个生命周期为程序周期的变量,其地址在任何地方都是有效的(动态链接加偏移量那种暂且不谈),只要你能拿到地址就能读,只要段不是只读的就能写。static是用来在编译时不把符号对外公布,链接时找不到地址从而实现对作为范围限制的,通过地址传递而非名称符号传递可以绕开这个限制。

总之,局部和全局可以用是不是在栈区来区分。static和visibility试图用链接的符号表来细分全局性,但是这不能从实质上影响到全局与否因为可以用指针绕开;extern则相反,是试图从其他编译单元或库找来一个自己没有的全局变量
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐