c语言中的内存知识

内存布局

任何一个程序,正常运行都需要内存资源,用来存放诸如变量、常量、函数代码等等。这些不同的内容,所存储的内存区域是不同的,且不同的区域有不同的特性。因此我们需要研究C语言进程的内存布局,逐个了解不同内存区域的特性。

每个C语言进程都拥有一片结构相同的虚拟内存,所谓的虚拟内存,就是从实际物理内存映射出来的地址规范范围,最重要的特征是所有的虚拟内存布局都是相同的,极大地方便内核管理不同的进程。例如三个完全不相干的进程p1、p2、p3,它们很显然会占据不同区段的物理内存,但经过系统的变换和映射,它们的虚拟内存的布局是完全一样的。

通过从实际物理内存中映射的这种方式,我们可以发现每一个进程它的结构都是一样的,这样就方便了我们对每个进程进行管理。

 下面我们将会更详细地来聊一聊各个区段地详细信息

栈内存

  • 什么东西存储在栈内存中?
    • 环境变量
    • 命令行参数
    • 局部变量(包括形参)
  • 栈内存有什么特点?
    • 空间有限,尤其在嵌入式环境下。因此不可以用来存储尺寸太大的变量。
    • 每当一个函数被调用,栈就会向下增长一段,用以存储该函数的局部变量。
    • 每当一个函数退出,栈就会向上缩减一段,将该函数的局部变量所占内存归还给系统。
  • 注意:
    栈内存的分配和释放,都是由系统规定的,我们无法干预。

环境变量

用于存储有关系统环境的信息,如路径、用户信息等。比如说在程序中一个和程序相同层次目录的文件的话不需要写出来文件完整的路径,因为在栈中的环境变量中存放了对应路径。

命令行参数

在C语言中,命令行参数是通过main函数的参数传递给程序的。main函数的签名通常有以下两种形式:

  1. int main(void)
  2. int main(int argc, char *argv[])

其中,第二种形式用于接收命令行参数:

  • argc(argument count)表示传递给程序的参数个数,包括程序名本身。
  • argv(argument vector)是一个指向字符串数组的指针,每个字符串表示一个参数。

这些参数在程序运行时会被传递到栈内存中。具体来说,当操作系统加载并执行程序时,会将命令行参数放在栈上,然后将相应的指针传递给main函数。

下面是一个简单的示例,演示如何在C语言中访问命令行参数:

#include <stdio.h>

int main(int argc, char *argv[]) {
    printf("Number of arguments: %d\n", argc);
    for (int i = 0; i < argc; i++) {
        printf("Argument %d: %s\n", i, argv[i]);
    }
    return 0;
}

假设编译后的程序名为program,如果在命令行中执行以下命令:

./program arg1 arg2 arg3

输出将会是:

Number of arguments: 4
Argument 0: ./program
Argument 1: arg1
Argument 2: arg2
Argument 3: arg3

这里是4的原因, ./program也被当成了字符串传入了进来

 

可以看到运行程序的时候跟着的那行命令中的所有内存都传递给了argc。可以看到这里第二个参数是一个指针数组,可以接收多个字符串。

我们同时也可以发现其实系统上来并不是直接进入到main函数来执行我们的程序,而是先执行了一些系统初始化程序,比如定义了argv数组并且可以用命令行参数给它赋值。随后系统才会调用我们的main函数。

下面三种写法等效

qt中也可以加命令行参数,在Commed line arguments这一栏中

局部变量

用于存储函数的局部变量、参数和返回地址等信息。包括我们的main函数和自定义函数

在调用的时候栈是动态发生变化的,当调用了一个新的函数时,这个函数中的局部变量和传入的参数都新加到栈中,当函数调用结束后,这块内存自动被释放。栈是一种先进后出的结构,比如我们的main函数最早进来的,main函数占用的栈空间是最后被释放的。

数据段内存

数据段用于存储静态数据(全局变量和静态局部变量)

C语言中,静态数据有两种:全局变量和静态变量,细分的话其实可以分为三种,即全局变量静态全局变量静态局部变量

  • 全局变量:定义在函数外部的变量,整个文件所有地方都可以访问。全局变量的定义全部文件可见,默认使用当前文件中可使用。可以通过extern使得其他文件中也可使用,什么意思呢?

就是说默认情况下,C语言中的全局变量具有外部链接(external linkage),这意味着它在整个程序的所有文件中都是可见的,在其他文件中不能重复定义这个变量,但是这个变量不能直接给其他文件来使用。其他文件要想使用这个全局变量,只能在其他文件中通过extern来声明一下才可使用。

  • 静态全局变量:定义在函数外部且被static修饰的变量,整个文件所有地方都可以访问。静态全局变量的定义当前文件可见,只能当前文件中可使用。因为定义是当前文件可见,所以其他文件中可以定义同名的静态全局变量。
  • 静态局部变量:定义在函数内部且被static修饰的变量,只有函数内部可以访问。静态局部变量的定义是仅仅当前函数可见,只能当前函数中可使用,所以其他函数中可以定义同名的静态全局变量。

数据段和栈内存,以及堆内存不一样的是占用的空间大小不可变,数据段中的数据可以发生变化,但是总的空间不变,也不会出现空间释放。全局变量和静态局部变量在程序结束的时候才释放。寿命很长。

  • 为什么需要静态数据?
  1. 全局变量主要是对于全部文件都可见,可以让不同文件之间都能访问这个数据
  2. 如果目的是仅当前文件所有函数可访问就行,那么就定义静态全局变量,可以方便地让所有函数之间共享这个数据,比如在写一些练习题时,将数组定义成静态全局变量可以避免函数传递。
  3. 当我们希望一个函数退出后依然能保留局部变量的值,以便于下次调用时还能用时,静态局部变量可帮助实现这样的功能。 static修饰的变量仅对当前函数有作用,static修饰的变量仅仅能当前函数内使用。
  • 注意:
    • 若定义时未初始化,则系统会将所有的静态数据自动初始化为0
    • 静态数据初始化语句,只会执行一遍。
    • 静态数据从程序开始运行时便已存在,直到程序退出时才释放。
    • 静态数据固然功能强大,但是不要滥用,要合理选择,比如变量都写成全局变量,就会污染其他文件中命名空间

除此之外,static还可以修饰函数

  • static修饰函数:使之由各文件可见的函数,变成了本文件可见的静态函数。如果在一个文件中定义了一个函数,在其他文件中不能再定义一个同名的函数。这是因为函数默认具有外部链接,名称在整个程序中必须是唯一的。希望在其他文件中使用这个函数,那么在其他文件中需要使用 extern 关键字进行声明。并且加上static之后,链接属性变为内部链接,其他文件也不可见这个函数的定义了,也能声明同名函数
  • ok那就再来补充一点吧,刚刚无论是全局变量,还是函数想要在其他文件中使用的话就必须extern,那么一个文件中的多个函数和全局变量被其他函数使用,extern声明岂不是很麻烦,确实,所以也可以直接包含其他文件的头文件

数据段的.bss,.data,.rodata简单了解一下就行

使用extern示例:

堆内存

堆内存的特点

  1. 动态分配:堆内存可以在程序运行时使用库函数如malloccallocrealloc进行动态分配。
  2. 手动释放:程序员需要使用free函数手动释放不再需要的堆内存,否则会导致内存泄漏。
  3. 不自动回收:与栈内存不同,堆内存不会在函数返回时自动释放,需要显式释放。
  4. 灵活性:适用于大小和数量在编译时未知的数据结构。

申请堆内存的正确且完备的过程

用完后free及时归还,将对应指针置空!!!

详细的malloccallocrealloc用法比较简单,可以自行搜索学习,这里不多加赘述

内存泄漏检测工具

linux下一款检测内存泄漏的工具:sudo apt-get install valgrind # 对于 Debian/Ubuntu 系统

当然qt下也可以通过按键直接来调用valgrind帮助检测。

  • 11
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值