C语言编程宏定义的优缺点,C语言重要知识点总结(二)--内存结构、函数调用过程(栈帧)、宏的优缺点以及##和#的使用...

一、内存结构

内存大致可以分为四个部分:代码段,静态存储区,堆,栈。

具体划分如下图所示:

f33f77c4d28e1ad2c1ed292e1ac7fc2c.png

栈:在执行函数时,函数内部局部变量的存储单元都可以在栈上创建,函数执行结束后会自动释放内存。栈内存的分配运算内置于处理器的指令中。效率高,但分配的内存容量有限,程序发生错误时,很有可能出现栈溢出。

堆:又称为动态内存分配区,程序在执行的时候用malloc或new申请指定大小的内存,程序员自己负责在任何时候用free或delete释放内存。若在申请后未释放,在程序结束时有可能会有OS自动回收,或者造成内存泄露,因此,使用时一定要注意。

静态存储区:包括程序中明确被初始化的全局变量、静态变量、常量以及未被初始化的数据。内存在程序编译时已经分配好,这块内存在整个运行期间一直存在。

代码区:存放程序代码,代码区是可以共享的(其他的执行程序可以调用它),整个内存中只有一份。

栈和堆的区别

1.管理方式不同

栈由编译器自动管理,堆由程序员控制。

2.空间大小不同

栈是向低地址扩展的结构,是连续的内存区域,因此栈顶和栈底是规定好的,容量有限,若用户使用过多的栈空间,会产生溢出。堆是向高地址扩展,不是连续的内存区域,堆获得的空间较大,分配时也比较灵活。

3.是否产生碎片

对于堆来说,频繁的malloc/free,new/delete会是空间不连续,造成大量碎片,使得程序效率降低。而栈不存在这样的问题。

4.增长方向不同

堆和栈相对生长

5.分配方式

堆由程序员通过使用malloc、free、new、delete来进行管理。栈由编译器申请释放,栈的动态内存分配通过alloca函数完成。

6.分配效率不同

栈是机器系统提供的数据结构,计算机在底层对栈提供支持。堆是C库函数提供的,堆的效率比栈低得多。

二、函数的调用过程(栈帧)

每一次的函数调用都是一个过程,我们将这个过程称为函数的调用过程。在这个过程中,会开辟栈空间用来保存本次函数调用过程中临时变量以及现场保护等工作,这块栈空间我们称之为函数栈帧。栈帧的维护需要两个寄存器也叫帧指针,即ebp和esp,esp中存放栈顶地址,ebp存放栈底地址。esp和ebp一次只能存放一个地址。

以下是函数调用的详细过程:

6d97009001a756dd45cb51c0f3ac11c7.png如图所示,

假设函数a调用了函数b,函数b调用了函数c,函数b有三个参数,从左至右依次为参数1,参数2,参数3。

1.函数a在调用函数b的时候,首先将函数b的参数以相反的顺序依次压入栈中,即,从最后一个参数开始压栈。

2.函数a使用call指令调用函数b,并将call指令下的一条指令的地址当做返回地址压入栈中。(汇编call命令的两个功能:1.保存当前指令的下一个指令的地址。2.pc指针跳转到调用函数的入口地址。)

3.在函数b的栈帧中,首先保存函数a的栈底地址,再将函数a的栈顶地址当做函数b的栈底地址,即图中所示的push ebp和mov ebp,esp这两条指令。

4.然后,从ebp的位置开始存放函数b的局部变量,将这些变量的地址依次存放在栈中,先定义的先入栈,后定义的后入栈。

注意:在不同的编译器上函数的调用过程可能会有所不同,但大致思想相同。

三、宏和函数

(一)宏和函数的区别:

1.宏只是做简单的字符串替换,与类型无关;而函数是参数的传递,参数有类型。

2.宏参数在进行替换时是直接替换,不进行任何计算,函数在调用时会计算形参的值。

3.宏替换发生在编译之前,而函数调用发生在编译之后。

4.宏替换不占内存空间,函数在调用时有栈帧,会占用栈上的空间。

5.使用宏的执行速度更快,函数调用有跳转、现场保护、返回过程等操作,因此有额外的时间开销。

6.每次使用宏都会进行替换,因此如果有大量的宏或者宏较长,大量的宏替换会使得代码长度大幅度增加;函数只有一个,每次使用都会调用同一个地方的函数,因此代码长度较短。

7.宏不能递归,函数可以。

(二)宏的优缺点

优点:

1.提高程序的可读性,方便修改

2.避免函数入栈,出栈的操作,提高运行效率,减少系统开销

3.宏是由预处理器处理的,通过字符串的替换操作可以完成很多编译器无法实现的功能,如连接字符等

4.宏与类型无关,更通用

缺点:

1.宏不能进行调试(预处理时直接进行文本替换,不检查类型,语法等)

2.宏与类型无关,不够严谨

3.宏可能会带来运算符优先级问题

4.造成代码长度大幅度增加

5.宏不能递归,函数式宏定义会导致代码执行效率降低

(三)# 和 ## 的使用

# :用来将宏变成字符串

## :拼接宏的名字或字符串

#include #define A(a) (#a)

#define B(b,c) b##c

int main()

{

printf(A(hahaha));

//这里是将hahaha转换成字符串

printf("\n");

printf("%d",B(33,44));

//这里的33和44是两个int型数字,组合后仍是一个int型数字3344,而不是字符串

printf("\n");

printf("%s\n",B("hhhh","aaaa"));

return 0;

}

运行结果:

hahaha

3344

hhhhaaaa

C语言重要知识点总结(一)–编译链接过程、数据类型、操作符

C语言重要知识点总结(三)–const、volatile、extern、static、restrict、register关键字解析

C语言重要知识点总结(四)–结构体、结构体内存对齐、位段、枚举、联合

以上总结如有不足之处,希望指出,谢谢!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值