吴章金: 实例解析 Linux C 语言程序之变量类型
2021-03-13 19:29
标签:attribute case 全局 概念 ade 类型 访问 upper ccm license: "cc-by-nc-nd-4.0" "本文从编译、二进制程序文件和运行角度逐级解析了 Linux C 语言程序中几种变量类型" 前几天,有同学在 “泰晓原创团队” 讨论群问道: 看到这个问题,立即浮现的概念是 RUN ONCE,内核源码找了一下: 代码示例1: 代码示例2: 另外,他又继续问道: 这个问题则上升到 C 语言关键字 static 的用法。 static 这个关键字用来限定某个变量或者函数的作用域,这个作用域可能是文件层面,也可能是函数层面。 从语法的角度去解释某个关键字用法的文章很多,可是这些解释蛮多时候是很生硬的,不是那么好记忆。 本文尝试从实操的角度去解析 static 以及更多类型的 C 语言变量的形态。 假如某个功能需求由多个文件构成如下: 编译运行如下: 类似这种需要跨文件访问的函数和变量,如果定义成 static 的话: 先来编译成一个中间的可重定位文件: 针对加 static 的情况: 不加的情况: LOCAL 和 GLOBAL 直观地反应了 static 用于限定变量和函数在文件之外是否可访问。加了 static 以后,文件之外不可见。 补充另外一个 nm 工具的结果,针对加 static 的情况: 不加的情况: 上面四个字母有两组大小写,分别对应 data, text 的 LOCAL 和 GOLOBAL 符号,其中 "hello" 是数据,“print” 作为函数处在代码区域。 延伸介绍到 nm 这个工具是因为,Linux 内核的 System.map 这样的符号表文件经常会被用来调试,这个文件实际上是用 nm 导出来的。 再延伸一个 WEAK 类型,这个类型类似于不加 static 的 GLOBAL,但是呢,允许定义另外一个同名的函数或者变量,用来覆盖 WEAK 类型的这个: 这种情况允许某个变量或者函数的“multiple definition”,如果不定义为 WEAK 类型而且不定义为 LOCAL(用 static),这种情况本来是不被允许的: 这种用法在内核中被广泛采用,通常用来确保可以添加架构特定的优化函数: 汇总如下: 上面从编译和二进制程序文件的角度分析了 static 关键字针对文件层面变量和函数的约定,下面再来看看函数内部的变量,在声明为 static 与否情况下的异同。 作为对比,把其他类型的变量也纳入进来: 用二进制程序文件来佐证: 综合上面 3 组数据: 再补充几点: 用 register 定义的变量存放在寄存器中,所以无法获取它们的内存地址(因为根本不存放在内存中)。可以通过查看汇编代码确认: 函数内用 static 定义的变量名(i 和 j)在符号表中都加了后缀,主要是方便多个函数定义同样的变量名,因为这些变量仅限该函数内(含多次调用)可见。 函数内非 static 定义的变量,以及函数参数的传递都是通过 Stack 完成的,这些变量只在函数内(包括 Caller, Callee)可见,外部不可见,所以在符号表中也找不到它们。 小结 大学的课程蛮多都停留在语法层面的描述,需要透彻理解一些“概念”,还是需要结合实际操作,从终极使用的角度来看这些“概念”,看到的深度和细节会大有不同。 (完) 吴章金: 实例解析 Linux C 语言程序之变量类型 标签:attribute case 全局 概念 ade 类型 访问 upper ccm 原文地址:https://blog.51cto.com/15015138/2555561
背景说明
请教下,谭 C,8.9.3,用 static 声明静态局部变量,在实际中可有案例。
$ grep -i "static.*run_once" -ur ./ --include "*.c"
./arch/mips/mm/page.c: static atomic_t run_once = ATOMIC_INIT(0);
./arch/mips/mm/page.c: static atomic_t run_once = ATOMIC_INIT(0);
./arch/mips/mm/tlbex.c: static int run_once = 0;
void build_clear_page(void)
{
static atomic_t run_once = ATOMIC_INIT(0);
if (atomic_xchg(&run_once, 1)) {
return;
}
/* body */
}
void build_tlb_refill_handler(void)
{
...
if (!run_once) {
/* body */
run_once++;
}
}
一般是用 static 定义一个全局的,很少看到函数内部在用 static?
从编译的角度
$ cat print.h
extern void print(char *str);
extern char *hello;
$ cat hello.c
#include "print.h"
int main(void)
{
print(hello);
return 0;
}
$ cat print.c
#include
$ gcc -m32 -o hello x.c print.h print.c
$ ./hello
hello
$ cat print.c
#include
从 ELF 二进制程序文件的角度
$ gcc -m32 -c -o print.o print.c
$ readelf -s print.o | egrep "hello$|print$"
6: 00000000 4 OBJECT LOCAL DEFAULT 3 hello
7: 00000000 23 FUNC LOCAL DEFAULT 1 print
$ readelf -s print.o | egrep "hello$|print$"
9: 00000000 4 OBJECT GLOBAL DEFAULT 3 hello
10: 00000000 23 FUNC GLOBAL DEFAULT 1 print
$ nm print.o | egrep "hello$|print$"
00000000 d hello
00000000 t print
$ nm print.o | egrep "hello$|print$"
00000000 D hello
00000000 T print
man nm:
"D"
"d" The symbol is in the initialized data section.
"T"
"t" The symbol is in the text (code) section.
If lowercase, the symbol is usually local; if uppercase, the symbol is global (external). There are however a few lowercase symbols that are shown for special global symbols ("u", "v" and "w")
$ cat print.c
#include
$ gcc -m32 -o hello x.c print.h print.c
/tmp/ccMO5y0A.o:(.data+0x0): multiple definition of `hello‘
/tmp/ccj8KK1s.o:(.data+0x0): first defined here
collect2: error: ld returned 1 exit status
$ grep __weak -ur ./ --include "*.c" | wc -l
413
关键字
类型
说明
static
LOCAL
限文件内访问
不加 static
GLOBAL
文件外可在 extern 声明后访问
weak attribute
WEAK
同 GLOBAL,但可重定义
从运行的角度
$ cat hello.c
#include
$ readelf -S hello | grep 804a | tail -2
[24] .data PROGBITS 0804a018 001018 000014 00 WA 0 0 4
[25] .bss NOBITS 0804a02c 00102c 000010 00 WA 0 0 4
$ readelf -s hello | egrep " m$| n$| a$| b$| i| j| x$| y$"
36: 0804a030 4 OBJECT LOCAL DEFAULT 25 m
37: 0804a020 4 OBJECT LOCAL DEFAULT 24 n
39: 0804a034 4 OBJECT LOCAL DEFAULT 25 i.2021
40: 0804a028 4 OBJECT LOCAL DEFAULT 24 j.2022
54: 0804a024 4 OBJECT GLOBAL DEFAULT 24 b
67: 0804a038 4 OBJECT GLOBAL DEFAULT 25 a
类型
代码
存储区域
文件内
static int m;
.bss(被初始化为 0)
文件内
static int n=1000;
.data
文件内
int a;
.bss(被初始化为 0), GLOBAL
文件内
int b = 10000;
.data, GLOBAL
函数内
static int i;
.bss(被初始化为 0)
函数内
static int j = 10;
.data
函数内
int x;
stack(值随机,未初始化)
函数内
int y = 100;
stack
函数内
register int z = 33;
register(汇编中分配好)
函数参数
argc, argv
stack(默认调用约定)
$ gcc -m32 -S -o hello.s hello.c
$ grep 33 hello.s
movl $33, %ebx