嵌入式系统:C语言基础(三)

1.volatile声明的作用

volatile声明的变量是指可能会被意想不到地改变的变量,这样编译器就不会轻易优化该变量。它主要用于多线程编程中,用来保证共享变量的内存可见性。(注:指针也可用volatile)

三个常见场景

  • 多线程中的共享变量
  • 中断程序中访问到的非自动变量
  • 并行设备的硬件寄存器

2.extern关键字

  • 声明一个在其他文件中定义的外部变量或函数。
  • 告诉编译器在链接过程中需要找到对应的定义。
  • 允许在当前文件中使用这些外部变量或函数而不需要重新定义。

举例:

// File1.c
extern int globalVar = 10;

// File2.c
#include <stdio.h>

extern int globalVar;

int main() {
   printf("globalVar 的值:%d\n", globalVar);
   return 0;
}
在 File1.c 中,我们使用 extern 关键字来定义一个全局变量 globalVar,并初始化为 10。
在 File2.c 中,我们使用 extern 关键字来声明同名的全局变量 globalVar,以表示它是在其他源文件中定义的。
然后,在 main 函数中,我们可以访问并打印 globalVar 的值。

3.什么是内存池?

1.定义:

内存池(Memory Pool)是一种数据结构,用于管理动态分配的内存块。内存池通过预先分配一定数量的内存块,并在程序运行期间重复使用这些内存块,以减少内存碎片化和提高内存分配效率。内存池通常用于需要频繁分配和释放内存的场景,例如在嵌入式系统、网络编程或图形处理等领域。通过内存池,可以减少内存分配的开销,提高程序的性能和效率。

2.原理:

在初始化阶段,内存池会预先分配一定数量的内存块,并建立一个空闲内存块的列表。当程序需要分配内存时,内存池会从空闲内存块列表中取出一个内存块分配给程序,并将该内存块从空闲列表中移除。当程序释放内存时内存池会将该内存块重新加入空闲内存块列表,以便后续重复利用。内存池会尽量重复利用预分配的内存块,避免频繁地向操作系统请求内存,从而减少内存碎片化和提高内存分配效率。内存池负责管理内存块的分配和释放,确保内存块的正确使用和避免内存泄漏。在程序结束时,内存池会释放所有未被使用的内存块,并进行清理工作,确保内存的正确释放。

4.指针使用的注意事项:

  1. 空指针检查:在使用指针之前,应该始终检查指针是否为空(NULL)。尝试在空指针上进行操作会导致程序崩溃或不可预测的行为。
  2. 避免野指针:避免使用未初始化的指针或已经释放的指针,这些指针称为野指针。使用野指针会导致程序出现不可预测的行为,甚至崩溃。
  3. 指针算术:在进行指针算术操作时,确保不会越界访问内存区域。指针算术应该基于指针指向的内存块大小进行计算,避免访问无效内存。
  4. 指针类型转换:当需要进行指针类型转换时,要确保转换是安全的。不同类型的指针之间的转换可能会导致数据损坏或未定义的行为。
  5. 动态内存管理:使用动态内存分配时,要确保在不再需要使用内存时及时释放内存,避免内存泄漏。
  6. 指针传递:在函数间传递指针时,要注意指针所指向的内存是否在函数调用期间保持有效。避免在函数结束后继续使用已被释放的内存。
  7. 指针的生命周期:要确保指针的生命周期不会超过其所指向的对象的生命周期,以避免悬空指针问题。
  8. 指针的安全性:在多线程环境下,要确保对指针的访问是线程安全的,可以通过加锁或其他同步机制来保证指针的安全访问。

5.在函数中申请堆内存需要注意什么?

(1)不要错误地返回指向“栈内存”的指针,因为该内存在函数结束时自动消亡。即函数内申请的临时数组,不要指望能够拿到数组内的内容,因为函数执行完成后,数组消亡。

(2)不要返回了常量区的内存空间。因为常量字符串,存放在代码段的常量区,生命期内恒定不变,只读不可修改。不可修改拿到也没有什么意义。

(3)通过传入一级指针不能解决,因为函数内部的指针将指向新的内存地址。

解决办法:

(1)使用二级指针

(2)通过指针函数解决,返回用malloc新申请的堆内存空间的地址,这样才能拿到内存内容。

6.什么是内存碎片?

内存碎片是指内存中未被有效利用的小块零散内存空间。内存碎片分为两种类型:外部碎片和内部碎片。

1.外部碎片:

  • 外部碎片是指内存中未被使用的零散内存块,这些内存块虽然总大小足够容纳需要分配的内存,但是它们分散在一起,无法满足连续内存分配的需求。外部碎片通常是由于频繁的内存分配和释放操作导致的,使得内存区域出现了分散的空闲块。

2.内部碎片:

  • 内部碎片是指已分配给程序的内存块中未被程序有效利用的部分。内部碎片通常是由于内存分配器为了对齐或其他目的而分配了比程序请求的更大的内存块,导致实际分配给程序的内存比需要的内存多出一些,而这部分多余的内存无法被程序利用,从而造成了内部碎片。

7.堆和栈的区别

堆栈空间分配不同:

  • 栈由操作系统自动进行分配和释放,用于存放函数的参数值、局部变量的值等,具有高效性。
  • 堆一般由程序员手动进行分配和释放,效率比栈低很多。

堆栈缓存方式不同:

  • 栈使用一级缓存,存储在处理器核心中,调用完成后立即释放,速度较快。
  • 堆存储在二级缓存或主存中,速度相对较慢。

生长方向

  • 堆:堆的分配方向是向上的,即向地址较大的方向分配。当堆需要扩展时,会向高地址方向增长。
  • 栈:栈的分配方向是向下的,即向地址较小的方向分配。当栈需要扩展时,会向低地址方向增长。

生命周期:

  • 堆:堆上的内存在分配时并不会被立即释放,需要手动进行内存释放操作。堆上的数据可以在程序的任意位置进行访问,不受函数的调用关系限制。
  • 栈:栈上的内存分配和释放是自动进行的,随着函数的调用和返回进行相应的操作。栈上的数据只在特定的作用域内有效,函数执行完成后会自动释放。

空间大小:

  • 栈的空间大小一般较小,通常最多为2MB,超过则会报溢出错误。
  • 堆的空间比较大,理论上可以接近3GB(对于32位程序来说)。

能否产生碎片:

  • 栈操作遵循"后进先出"的原则,不会有内存块从栈中弹出,因此不会产生碎片。
  • 堆是通过动态分配内存的方式进行分配和释放,频繁的申请和释放内存可能会引发内存碎片问题。

8.初始化为0的全局变量在bss还是data

初始化为0的全局变量通常会被分配到程序的BSS(Block Started by Symbol)段。BSS段是用于存放未初始化或初始化为0的全局变量和静态变量的一部分内存空间。在程序加载时,系统会自动将BSS段中的变量初始化为0。

已经明确初始化为非零值的全局变量会被分配到程序的数据(Data)段。Data段用于存放已经初始化的全局变量和静态变量。

总体来说,初始化为0的全局变量通常会被分配到BSS段,而已初始化为非零值的全局变量则会被分配到Data段。

#一人推荐一个机械人值得去的公司##牛客在线求职答疑中心##机械人春招想让哪家公司来捞你?##牛客解忧铺##我的实习求职记录#
嵌入式知识图谱 文章被收录于专栏

欢迎来到《嵌入式知识图谱》专栏!这里是一个专注于涵盖C/C++编程、操作系统原理、数据结构算法、计算机网络技术以及嵌入式软件知识的平台。 在本专栏中,我们将分享关于嵌入式系统开发的最新趋势、实用技巧、行业见解以及面试准备建议。无论您是刚入门的学习者还是经验丰富的专业人士,我们都致力于为您提供有价值的内容,帮助您在嵌入式软件领域取得成功。

全部评论

相关推荐

点赞 8 评论
分享
牛客网
牛客企业服务