带参宏和函数的区别
• 宏在预处理阶段展开,函数在编译链接阶段被调用执行。
• 宏展开时只是简单的文本替换,不进行类型检查和参数校验,容易出错;函数会进行参数类型检查和参数数量校验,安全性更高。
• 宏可以定义不定参数数量的宏,函数需要使用可变参数的形式才能实现。
#define和typedef区别
• #define用来定义宏,是对文本进行简单的替换;typedef用来定义新的类型名,可以对类型进行封装和抽象。
• #define定义的宏没有类型检查和作用域限制,可以定义在任何位置;typedef定义的新类型是有类型的,需要在定义后才能使用。
define的使用
#define用于定义宏,通常用来简化代码,提高代码的可读性和可维护性。常见的用法包括:
• 定义常量:如#define PI 3.14159
• 定义函数宏:如#define SQUARE(x) ((x) * (x))
• 定义条件编译宏:如#define DEBUG
#define宏定义的缺陷
1) 它无法进行类型检查(主要是为了判断变量或者参数的实际类型和声明的类型是否匹配的问题)。宏定义是在编译前进行字符的替换,因为还没有编译,不能编译前就检查好类型是否匹配,而只能在编译时才知道,所以不具备类型检查功能。
2) 由于宏定义的时候,其各个分量未加括号,而在使用宏定义的时候,传递的参数是变量的表达式,然后经过系统展开后,由于优先级的原因,导致其结果不是你所希望的。特殊情况时候,加了括号也无法避免错误(在宏定义中出现++或--之类的操作符的时候)
##的含义
在宏定义中,##号表示连接符,可以将两个宏参数连接成一个单词。例如:
#define CONCAT(a, b) a##b
printf("%d\n", CONCAT(1, 2)); // 输出12
在宏展开时,编译器将参数a和b连接成一个单词"12",然后作为整数常量传递给printf函数。
宏定义是在编译的哪个阶段被处理的?
宏定义是在预处理阶段被处理的,也就是在编译之前。在预处理阶段,编译器会扫描源文件中的宏定义,并将宏展开成对应的代码。然后再将展开后的代码进行编译和链接。因此,宏定义可以用来实现一些与平台无关的功能,例如预处理器常量和条件编译等。
变量声明和定义区别
变量的声明和定义是不同的概念。变量的定义是指在程序中为变量分配内存空间并指定初始值,它会在编译时被分配存储空间。变量的声明是指在程序中声明变量的类型和名称,告诉编译器该变量已经存在或将在其他文件中定义,它不会分配存储空间。
局部变量能否和全局变量重名?
可以。在C语言中,局部变量和全局变量的作用域是不同的,因此可以重名。
如何引用一个已经定义过的全局变量?
可以在其他文件中使用extern关键字来引用已经定义过的全局变量,例如:
// 在一个文件中定义全局变量
int global_var;
// 在另一个文件中引用全局变量
extern int global_var;
全局变量可不可以定义在可被多个.C文件包含的头文件中?
可以,但不推荐这么做。在头文件中定义全局变量会导致多个源文件中都包含了该变量的定义,容易引起命名冲突和重复定义等问题,应该将全局变量的定义放在一个源文件中,并使用extern关键字在其他源文件中引用它。
全局变量和局部变量的区别
全局变量定义在函数外部,具有全局作用域,从定义处到文件结尾都可以访问它;而局部变量定义在函数内部,只在函数内部有效,函数外部无法访问。
全局变量和static变量的区别
全局变量和static变量都有全局作用域,但是static变量仅在定义它的源文件中可见,不能被其他源文件访问。另外,static变量在程序启动时就被初始化,而全局变量则在程序首次使用时被初始化。
内存分配方式
在C语言中,内存可以分配在静态存储区、堆区和栈区三个地方。全局变量和static变量都分配在静态存储区,而动态分配的内存(例如使用malloc函数)则分配在堆区。局部变量和函数参数都分配在栈区。
栈在C语言中有什么作用
栈是一种数据结构,它在程序运行时用于存储函数的调用帧,以及一些临时变量。栈的作用是维护函数调用的返回地址、参数和局部变量等信息,从而实现函数调用的正确性和可靠性。
C语言函数参数压栈顺序是怎样的?
在C语言中,函数参数的压栈顺序是从右往左,即先压入最后一个参数,最后压入第一个参数。
函数调用的压栈过程
函数调用时,需要先将返回地址、函数参数和函数局部变量压入栈中,然后跳转到函数体执行。当函数返回时,需要将栈顶的返回地址弹出,恢复到函数调用点继续执行。
将临时变量作为返回值时的处理过程
当将临时变量作为函数的返回值时,需要将返回值存储在寄存器中或者使用堆内存分配动态存储空间,返回指向该内存的指针。这样可以确保返回值在函数返回后仍然有效。
printf函数的实现原理是什么?
printf函数的实现原理是使用系统调用向标准输出(stdout)输出字符串。具体来说,printf函数将要输出的字符串先存储在缓冲区中,当缓冲区满了或者遇到换行符时,将缓冲区中的字符串通过系统调用写入标准输出。
堆与栈区别
• 内存分配方式:堆上的内存分配是动态的,可以在程序运行期间动态地分配和释放。程序员需要通过内存分配函数(如或)来分配堆内存,并在不再需要时使用释放函数(如或)进行回收。栈上的内存分配是静态的,编译器自动分配和回收。局部变量和函数调用时的参数通常存储在栈上。
• 生命周期:堆上的内存生命周期在分配内存后,一直持续到程序员显式释放它。这意味着在多个函数调用之间,堆上的数据可以一直保留。栈上的内存生命周期与函数的调用周期相关。函数返回时,分配在栈上的内存会自动被释放。
• 碎片化问题:堆上的内存分配可能导致内存碎片化问题,因为内存分配和释放的顺序可能是不连续的。长时间运行的程序可能导致堆空间中出现许多空闲碎片,从而降低内存利用率。而栈上的内存分配和回收顺序是线性的,不存在内存碎片化问题。
• 存储空间大小:栈空间通常相对较小,受到操作系统限制。如果栈空间过大,可能导致栈溢出。堆空间相对较大,受限于计算机的可用内存。因此,对于较大的内存需求,通常需要使用堆空间进行分配。
• 访问速度:栈上的内存访问速度通常比堆上的内存访问速度要快。这是因为栈上的内存分配和回收操作只涉及指针移动,而堆上的内存分配和回收操作需要在内存中搜索可用空间,可能导致性能下降。
• 线程安全性:堆是线程共享的内存区域,分配在堆上的内存可以在多个线程之间共享。然而,这可能导致线程安全问题,因为多个线程可能同时访问和修改堆上的内存。因此,使用堆时需要注意同步和互斥。而栈是线程独立的内存区域,每个线程都有自己的栈,栈上的数据在默认情况下仅对当前线程可见,这意味着栈上的数据在某种程度上具有线程安全性。
什么情况下会栈溢出?如何避免?
栈溢出指当程序在执行过程中,向栈中写入了超过栈的内存空间范围的数据,导致覆盖了其他数据或者程序的关键信息,从而导致程序崩溃或者安全漏洞。栈溢出通常出现在以下几种情况下:
- 递归调用层数过多。
- 函数中使用了大量的局部变量。
- 函数中使用了大量的参数。
为了避免栈溢出,可以采取以下几种措施:
- 尽量避免使用递归调用,可以使用循环代替递归。
- 合理使用动态内存分配,尽量将局部变量转化为全局变量或静态变量。
- 减少函数参数的数量和大小,可以通过将大量参数打包成结构体或数组的方式来解决。
- 尽量减少代码中的不安全操作,例如使用strcat和strcpy等函数时要确保源字符串不会溢出目标字符串的大小。
在局部数组中定义一个大数组可以吗?
在局部数组中定义一个大数组可以,但是需要注意数组大小和栈区大小的关系。如果定义的数组太大,超出了栈区的大小限制,就会导致栈溢出的问题。
你觉得堆快一点还是栈快一点?
通常来说,栈的内存分配速度比堆要快,因为栈的内存分配是由系统自动完成的,而堆的内存分配需要调用malloc等函数,需要进行额外的操作。
堆的动态申请释放时注意的点
在堆中动态申请内存时,需要注意以下几点:
• 在使用完堆内存后,必须显式地释放,否则会导致内存泄漏。
• 释放堆内存后,必须避免继续使用指向该内存的指针,否则会出现悬垂指针的问题。
• 在动态申请内存时,必须检查申请是否成功,避免出现空指针的问题。
给已free的指针赋值
在给已free的指针赋值时,会出现悬垂指针的问题,因为指针指向的内存已经被释放,不能再使用。为了避免出现悬垂指针的问题,应该将指针赋值为NULL,表示该指针不指向任何有效的内存地址。
被free回收的内存是立即返还给操作系统吗?
被free回收的内存并不是立即返还给操作系统,而是由堆管理器进行管理。堆管理器会将被释放的内存块标记为空闲状态,然后将其加入空闲块链表中,以备后续的内存分配使用。
malloc的底层是如何实现的?
malloc的底层实现通常使用了操作系统提供的内存管理机制,例如使用了brk和sbrk系统调用来扩展堆区空间。在堆管理器的帮助下,malloc会在堆区中分配一块指定大小的内存,并返回指向该内存块的指针。
malloc能申请多大的空间?
在32位系统中,malloc函数能够申请的空间最大值通常是2GB,而在64位系统中,malloc函数能够申请的空间最大值通常是几百GB。
malloc和new的区别
• 用法不同:malloc()是C语言中的标准库函数,需要包含头文件<stdlib.h>,而new()是C++中的关键字,可以使用C++的对象和类。在使用new()时,需要使用类名或者类型名来指定分配的内存空间大小。
• 返回类型不同:malloc()的返回类型是void*,需要进行强制类型转换后才能使用,而new()返回的是指定类型的指针,可以直接使用。
• 内存分配方式不同:malloc()只是分配内存空间,并不会调用类的构造函数来初始化内存空间,而new()不仅会分配内存空间,还会调用类的构造函数来初始化内存空间,确保内存空间的正确使用。
• 内存释放方式不同:malloc()分配的内存空间需要使用free()函数来释放,而new()分配的内存空间需要使用delete关键字来释放,delete操作不仅会释放内存空间,还会调用类的析构函数来清理内存空间。