C语言作用域与内存布局

在这里插入图片描述

欢迎关注博主 Mindtechnist 或加入【Linux C/C++/Python社区】一起探讨和分享Linux C/C++/Python/Shell编程、机器人技术、机器学习、机器视觉、嵌入式AI相关领域的知识和技术。



专栏:《精通C语言》


作用域

C语言变量的作用域分为:

  • 代码块作用域(代码块是大括号{}之间的一段代码)
  • 函数作用域
  • 文件作用域

1. 局部变量

局部变量也叫auto自动变量(auto关键字可以省略不写),一般情况下代码块{}内部定义的变量都是局部变量,它有如下特点:

  • 在一个函数内定义,只在函数范围内有效
  • 在复合语句中定义,只在复合语句中有效
  • 随着函数调用的结束或复合语句的结束,即作用域的结束,局部变量的生命周期也结束
  • 如果没有给局部变量赋初值,那么随机变量的值为随机值
#include <stdio.h>

void test()
{
	//auto只能出现在{}内部
	auto int b = 10; //相当于  int b = 10; //auto可省略
}

int main(void)
{
	//b = 100; //error, 在main作用域中没有b, b的生命周期在test()内部

	if (1)
	{
		//在复合语句中定义,只在复合语句中有效
		int a = 10;
		printf("a = %d\n", a);
	}

	//a = 10; //error离开if()的复合语句,a生命周期结束
	
	return 0;
}

2. 静态局部变量

static修饰的局部变量称为静态局部变量

  • static局部变量的作用域也是在定义的函数内有效
  • static局部变量的生命周期和程序运行周期一样,同时staitc局部变量的值只初始化一次,但可以赋值多次(记忆功能)
  • static局部变量若未赋以初值,则由系统自动赋值,数值型变量自动赋初值0,字符型变量赋空字符
#include <stdio.h>

void fun1()
{
	int i = 0;
	i++;
	printf("i = %d\n", i);
}

void fun2()
{
	//静态局部变量,没有赋值,系统赋值为0,而且只会初始化一次
	static int a;
	a++;
	printf("a = %d\n", a);
}

int main(void)
{
	fun1();
	fun1();
	fun2();
	fun2();
	
	return 0;
}

3. 全局变量

  • 在函数外定义,可被本文件及其它文件中的函数所使用,如果其它文件中的函数想要调用此变量,必须通过extern声明为外部定义
  • 全局变量的生命周期和程序运行周期一样
  • 不同文件的全局变量不可重名

4. 静态全局变量

static修饰的全局变量称为静态全局变量

  • 在函数外定义,作用域被限制在所定义的文件中
  • 不同文件静态全局变量可以重名,但作用域不冲突
  • static全局变量的生命周期和程序运行周期一样,同时staitc全局变量的值只初始化一次

5. extern声明全局变量

通过extern声明一个变量,表示这个变量在别的文件中已经定义了,这里只是声明,而不是定义。

6. 全局函数和静态函数

在C语言中函数默认都是全局的,使用关键字static可以将函数声明为静态,函数被声明为static静态函数就意味着这个函数只能在定义这个函数的文件中使用,在其他文件中不能调用,即使在其他文件中声明这个函数都没用。对于不同文件中的staitc函数名字可以相同。

注意:

  • 允许在不同的函数中使用相同的变量名,它们代表不同的对象,分配不同的单元,互不干扰。
  • 同一源文件中,允许全局变量和局部变量同名,在局部变量的作用域内,全局变量不起作用。
  • 所有的函数默认都是全局的,意味着所有的函数都不能重名,但如果是staitc函数,那么作用域是文件级的,所以不同的文件static函数名是可以相同的。

内存布局

1. 内存分区

C源代码经过预处理、编译、汇编、链接4步后生成一个可执行程序。
在 Linux 下,程序是一个普通的具有可执行权限的文件,以下列出一个二进制可执行文件的基本情况:
在这里插入图片描述

通过上图可以得知,在没有运行程序前,也就是说程序没有加载到内存前,可执行程序内部已经分好3段信息,分别为代码区(text)、数据区(data)和未初始化数据区(bss)3 个部分(有时候也可以把data和bss合起来叫做静态区或全局区)。

  • 代码区:存放 CPU 执行的机器指令。通常代码区是可共享的(即另外的执行程序可以调用它),使其可共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可。代码区通常是只读的,使其只读的原因是防止程序意外地修改了它的指令。另外,代码区还规划了局部变量的相关信息。

  • 全局初始化数据区/静态数据区(data段):该区包含了在程序中明确被初始化的全局变量、已经初始化的静态变量(包括全局静态变量和局部静态变量)和常量数据(如字符串常量)。

  • 未初始化数据区(又叫 bss 区):存入的是全局未初始化变量和未初始化静态变量。未初始化数据区的数据在程序开始执行之前被内核初始化为 0 或者空(NULL)。

程序在加载到内存前,代码区和全局区(data和bss)的大小就是固定的,程序运行期间不能改变。然后,运行可执行程序,系统把程序加载到内存,除了根据可执行程序的信息分出代码区(text)、数据区(data)和未初始化数据区(bss)之外,还额外增加了栈区、堆区。
在这里插入图片描述

  • 代码区(text segment)
    加载的是可执行文件代码段,所有的可执行代码都加载到代码区,这块内存是不可以在运行期间修改的。

  • 未初始化数据区(BSS)
    加载的是可执行文件BSS段,位置可以分开亦可以紧靠数据段,存储于数据段的数据(全局未初始化,静态未初始化数据)的生存周期为整个程序运行过程。

  • 全局初始化数据区/静态数据区(data segment)
    加载的是可执行文件数据段,存储于数据段(全局初始化,静态初始化数据,文字常量(只读))的数据的生存周期为整个程序运行过程。

  • 栈区(stack)
    栈是一种先进后出的内存结构,由编译器自动分配释放,存放函数的参数值、返回值、局部变量等。在程序运行过程中实时加载和释放,因此,局部变量的生存周期为申请到释放该段栈空间。

  • 堆区(heap)
    堆是一个大容器,它的容量要远远大于栈,但没有栈那样先进后出的顺序。用于动态内存分配。堆在内存中位于BSS区和栈区之间。一般由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收。

2. 存储类型

类型作用域生命周期存储位置
auto变量代码块{}内当前函数栈区
static局部变量代码块{}内整个程序运行期初始化在data段,未初始化在BSS段
extern变量整个程序整个程序运行期初始化在data段,未初始化在BSS段
static全局变量当前文件整个程序运行期初始化在data段,未初始化在BSS段
extern函数整个程序整个程序运行期代码区
static函数当前文件整个程序运行期代码区
register变量代码块{}内当前函数运行时存储在CPU寄存器
字符串常量当前文件整个程序运行期data段

3. 内存操作函数

  • memset

#include <string.h>
void *memset(void *s, int c, size_t n);
功能:将s的内存区域的前n个字节以参数c填入
参数:
s:需要操作内存s的首地址
c:填充的字符,c虽然参数为int,但必须是unsigned char , 范围为0~255
n:指定需要设置的大小
返回值:s的首地址

  • memcpy/memmove

#include <string.h>
void *memcpy(void *dest, const void *src, size_t n);
功能:拷贝src所指的内存内容的前n个字节到dest所值的内存地址上。
参数:
dest:目的内存首地址
src: 源内存首地址,注意, dest和src所指的内存空间不可重叠
n: 需要拷贝的字节数
返回值:dest的首地址
memmove()功能用法和memcpy()一样,区别在于dest和src所指的内存空间重叠时,memmove()仍然能处理,不过执行效率比memcpy()低些。

  • memcmp

#include <string.h>
int memcmp(const void *s1, const void *s2, size_t n);
功能:比较s1和s2所指向内存区域的前n个字节
参数:
s1:内存首地址1
s2:内存首地址2
n:需比较的前n个字节
返回值:
相等:=0
大于:>0
小于:<0


在这里插入图片描述
在这里插入图片描述


posted @ 2022-12-14 15:26  Mindtechnist  阅读(15)  评论(0编辑  收藏  举报  来源