返回介绍

1. 变量

发布于 2024-10-12 21:58:08 字数 3573 浏览 0 评论 0 收藏 0

变量(variable)与对象(object)联系在一起。

对象是执行期间的存储区域,其内容表示值(value),以特定类型(type)进行解释。变量则是对象外在身份,用以描述对象基本信息,诸如内存地址、数据类型,以及作用域等。对象可通过类型转换或多态方式拥有不同身份。

我们可以定义(define)或声明(declare)一个变量。定义有明确内存分配行为,依照类型决定大小,或赋予初始值;声明则向编译器说明目标信息,如名字、类型和参数等。但不一定会为其分配内存,因为它或许已在某处定义过,声明只是告知该如何使用而已。显然,定义是声明的一种,反之则不然。

一个变量可以被声明多次,但只能被定义一次。

int x = 1;

单行定义多个变量时,它们的类型未必相同。

char *p, c;                           // char*, char
static_assert(sizeof(p) == 8);
static_assert(sizeof(c) == 1);
int x, y[5];                          // int, int[5]
int m[12], n[15][3], o[21];           // int[12], int[15][3], int[21]

并没有强制要求变量初始化,也就意味着值 “不可预知”。

int *p;
printf("%d\n", *p);   // Segmentation fault
int a, b = 10;        // a uninitialized; b = 10

按值传递(pass-by-value),也就是说赋值、传参或返回值,总是进行拷贝。

无非是复制目标对象,还是指针自身。

int x = 1;
int y = x;

assert(&x != &y);

全局变量

全局变量默认可被外部访问,可用 static 修饰符限定在当前模块(源码文件)内使用。

按是否有初始化值,分配到 .data 或 .bss 段内。(自动零值),静态修饰符仅仅改变该符号作用域,并不影响内存分配方式。尽量减少可被外部访问的全局变量。

int x = 0x11;             // .data
static int y = 0x22;      // .data
int z;                    // .bss   (zero-value)

int main (void)
{
    printf("%d, %d, %d\n", x, y, z);
    return EXIT_SUCCESS;
}

局部变量

默认局部变量生命周期仅限当前栈帧,或某个更小的作用域。

其内存分配位置为栈帧(stack frame),强烈建议初始化。

int test()
{
    int x = 100;
    ++x;

    return x;
}
$ objdump -d -M intel ./test | grep -A9 "<test>:"
    
0000000000401106 <test>:
  401106:	f3 0f 1e fa          	endbr64 
  40110a:	55                   	push   rbp
  40110b:	48 89 e5             	mov    rbp,rsp
  40110e:	c7 45 fc 64 00 00 00 	mov    DWORD PTR [rbp-0x4],0x64   # x = 100
  401115:	83 45 fc 01          	add    DWORD PTR [rbp-0x4],0x1    # ++x
  401119:	8b 45 fc             	mov    eax,DWORD PTR [rbp-0x4]    # return x
  40111c:	5d                   	pop    rbp
  40111d:	c3                   	ret  

如添加静态修饰,则存储位置变为 .data 或 .bss,并持有最后一次值。

int test()
{
    static int x = 100;   // 初始化值保存到 .data,并未使用代码进行初始化。
    ++x;

    return x;
}
$ objdump -dS -M intel ./test | grep -A20 "<test>:"
    
0000000000401136 <test>:
#include <stdio.h>
#include <stdlib.h>

int test()
{
  401136:	f3 0f 1e fa          	endbr64 
  40113a:	55                   	push   rbp
  40113b:	48 89 e5             	mov    rbp,rsp
    static int x = 100;
    ++x;
  40113e:	8b 05 ec 2e 00 00    	mov    eax,DWORD PTR [rip+0x2eec]  # 404030 <x.2832>
  401144:	83 c0 01             	add    eax,0x1
  401147:	89 05 e3 2e 00 00    	mov    DWORD PTR [rip+0x2ee3],eax  # 404030 <x.2832>

    return x;
  40114d:	8b 05 dd 2e 00 00    	mov    eax,DWORD PTR [rip+0x2edd]  # 404030 <x.2832>
}
  401153:	5d                   	pop    rbp
  401154:	c3                   	ret
$ readelf -x .data ./test

Hex dump of section '.data':
  0x00404020 00000000 00000000 00000000 00000000 ................
  0x00404030 64000000                            d...

外部变量

使用 extern 修饰符声明在其他模块定义的变量或函数。

前置声明:使用对象(变量或函数)前,需要先声明。模块向外提供函数或全局变量,通常使用头文件进行声明。

// lib.c

int X = 100;
#include <stdio.h>
#include <stdlib.h>

extern int X;

int main (void)
{
    printf("%d\n", X);
    
    return 0;
}
$ gcc -o ./test main.c lib.c && ./test
100

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文