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

一、全局和局部的可见范围
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链接后被放到了可执行程序的.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则相反,是试图从其他编译单元或库找来一个自己没有的全局变量

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值