C语言作用域与内存布局

简介: 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

相关文章
|
1月前
|
存储 编译器 程序员
【C语言】内存布局大揭秘 ! -《堆、栈和你从未听说过的内存角落》
在C语言中,内存布局是程序运行时非常重要的概念。内存布局直接影响程序的性能、稳定性和安全性。理解C程序的内存布局,有助于编写更高效和可靠的代码。本文将详细介绍C程序的内存布局,包括代码段、数据段、堆、栈等部分,并提供相关的示例和应用。
48 5
【C语言】内存布局大揭秘 ! -《堆、栈和你从未听说过的内存角落》
|
28天前
|
机器学习/深度学习 人工智能 缓存
【AI系统】推理内存布局
本文介绍了CPU和GPU的基础内存知识,NCHWX内存排布格式,以及MNN推理引擎如何通过数据内存重新排布进行内核优化,特别是针对WinoGrad卷积计算的优化方法,通过NC4HW4数据格式重排,有效利用了SIMD指令集特性,减少了cache miss,提高了计算效率。
45 3
|
1月前
|
存储 缓存 算法
【C语言】内存管理函数详细讲解
在C语言编程中,内存管理是至关重要的。动态内存分配函数允许程序在运行时请求和释放内存,这对于处理不确定大小的数据结构至关重要。以下是C语言内存管理函数的详细讲解,包括每个函数的功能、标准格式、示例代码、代码解释及其输出。
64 6
|
2月前
|
传感器 人工智能 物联网
C 语言在计算机科学中尤其在硬件交互方面占据重要地位。本文探讨了 C 语言与硬件交互的主要方法,包括直接访问硬件寄存器、中断处理、I/O 端口操作、内存映射 I/O 和设备驱动程序开发
C 语言在计算机科学中尤其在硬件交互方面占据重要地位。本文探讨了 C 语言与硬件交互的主要方法,包括直接访问硬件寄存器、中断处理、I/O 端口操作、内存映射 I/O 和设备驱动程序开发,以及面临的挑战和未来趋势,旨在帮助读者深入了解并掌握这些关键技术。
55 6
|
2月前
|
存储 C语言
C语言如何使用结构体和指针来操作动态分配的内存
在C语言中,通过定义结构体并使用指向该结构体的指针,可以对动态分配的内存进行操作。首先利用 `malloc` 或 `calloc` 分配内存,然后通过指针访问和修改结构体成员,最后用 `free` 释放内存,实现资源的有效管理。
161 13
|
2月前
|
大数据 C语言
C 语言动态内存分配 —— 灵活掌控内存资源
C语言动态内存分配使程序在运行时灵活管理内存资源,通过malloc、calloc、realloc和free等函数实现内存的申请与释放,提高内存使用效率,适应不同应用场景需求。
|
2月前
|
存储 算法 程序员
C 语言指针详解 —— 内存操控的魔法棒
《C 语言指针详解》深入浅出地讲解了指针的概念、使用方法及其在内存操作中的重要作用,被誉为程序员手中的“内存操控魔法棒”。本书适合C语言初学者及希望深化理解指针机制的开发者阅读。
|
2月前
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
65 1
|
2月前
|
存储 C语言 计算机视觉
在C语言中指针数组和数组指针在动态内存分配中的应用
在C语言中,指针数组和数组指针均可用于动态内存分配。指针数组是数组的每个元素都是指针,可用于指向多个动态分配的内存块;数组指针则指向一个数组,可动态分配和管理大型数据结构。两者结合使用,灵活高效地管理内存。
|
7月前
|
程序员 C语言 C++
【C语言基础】:动态内存管理(含经典笔试题分析)-2
【C语言基础】:动态内存管理(含经典笔试题分析)