1.static的作用
1. 修饰全局变量:不可以被外部文件访问,只可以在本文件中使用,限定它的作用域
2. 修饰函数:不可以被外部文件访问,只可以在本文件中使用,限定函数的作用域
如果想要其他文件可以引用本地的函数,则要在函数定义时使用关键字,extern,表示该函数是外部函数可以被其他文件调用,另外要在引用这个函数的文件中使用extern声明要用的外部函数即可。
3. 修饰局部变量:延长局部变量的声明周期,局部变量的作用域没变。只有在第一次执行时进行初始化,后边不在进行初始化
4.static修饰的变量通常存在于静态存储区域
2.const的作用
在C语言中,const关键字有多种作用:
(1)用于定义常量:将变量声明为只读,其值不能被修改。const int a = 10;
1. 修饰局部变量:在栈区分配空间,可以通过指针进行间接修改,不可以通过自身修改
2. 修饰全局变量:在rodata段分配空间,不可以通过指针间接修改,也不肯通过自身修改
(2)用于函数参数类型声明:声明函数的形参为只读,在函数内部不允许修改该参数的值。void foo(const int a);
(3)用于指针类型声明:声明指针指向的内存区域为只读,指针所指向的内存区域的值不能被修改。const int *p;
用于指针类型声明:声明指针内存区域为只读,指针的内存区域的值(指针的指向)不能被修改。int *const p;
(4)用于结构体、联合体成员的定义:将结构体、联合体中的某个成员变量声明为只读,其值不能被修改。
struct MyStruct { const int a; int b; };
(5)用于函数返回值类型声明:声明函数的返回值类型为只读,该函数返回的值不能被修改。 const int foo();
3.全局变量和局部变量在内存中是否有区别?如果有,区别是什么?
存储位置:
全局变量:
存储在全局数据区(Global Data Area)中。这是程序运行时分配的一块内存区域,全局变量在整个程序执行期间都存在,从程序启动到结束。
局部变量:
存储在栈区(Stack)中。栈区是在函数调用时动态分配的内存区域,局部变量的存储空间在函数调用时创建,在函数执行结束后被释放。
生命周期:
全局变量: 生命周期等同于整个程序的执行周期。它在程序启动时被创建,在程序结束时被销毁。
局部变量: 生命周期与所在函数的执行周期相关。它在函数调用时被创建,函数执行结束后被销毁。
作用域:
全局变量: 全局变量在整个程序中可见,可以被任何函数访问。其作用域是整个程序。
局部变量: 局部变量只在定义它的函数中可见,称为局部作用域。 它对其他函数是不可见的,只在所在函数的范围内有效。
初始化:
全局变量: 如果全局变量没有显式初始化,将被默认初始化为零或空,具体取决于变量的类型。
局部变量: 局部变量的值在定义时不会自动初始化,它们的值是不确定的,除非显式初始化。
线程安全性:
全局变量: 在多线程环境中,全局变量的访问可能需要考虑线程同步的问题,以防止竞争条件。 局部变量: 局部变量一般不涉及线程安全性问题,因为它们只在函数的执行期间存在,每个线程有自己的栈。
4.局部变量和全局变量是否可以重名?
局部变量和全局变量可以重名,但是它们位于不同的作用域,因此在特定的作用域内,编译器会根据就近原则选择使用哪个变量。
5.全局变量可不可以定义在可被多个.C文件包含的头文件中?为什么?
全局变量可以定义在可被多个.C
文件包含的头文件中,但是这样可能会导致一些问题。全局变量的定义会在包含该头文件的每个.C
文件中生成一份拷贝,这可能导致链接时的冲突或者意外的行为。
通常来说,应该将全局变量的定义放在一个.C
文件中,并且在需要使用这个全局变量的其他文件中使用extern
关键字来声明该变量。这样可以确保只有一个全局变量的实例被创建,并且可以避免链接时的冲突。
6.简述程序的内存分配
7.解释堆区和栈区的区别?
-
栈区(Stack):
- 栈区是程序运行时自动分配和释放的内存区域,用于存储函数的局部变量、函数的参数值以及函数调用过程中的返回地址等临时数据。
- 栈区的大小和生命周期由编译器自动管理,每个线程都有自己的栈,栈的大小在程序编译时就已经确定。
- 栈区的内存分配和释放是按照后进先出(LIFO)的原则进行的,即最后分配的内存先被释放。
-
堆区(Heap):
- 堆区是动态分配的内存区域,用于存储程序运行时动态申请的数据,例如使用
malloc()
、calloc()
、realloc()
等函数分配的内存、全局变量等。 - 堆区的大小和生命周期不受编译器控制,需要程序员手动分配和释放内存。堆中的内存分配和释放通常由程序员负责,需要调用特定的函数进行操作。
- 堆区的内存分配和释放是动态的,可以根据需要随时进行分配和释放,但是需要注意避免内存泄漏和内存碎片等问题。
- 堆区是动态分配的内存区域,用于存储程序运行时动态申请的数据,例如使用
8.简述gcc四部曲
9.volatile关键字
-
防止编译器优化: 编译器在编译过程中可能会对变量进行优化,例如将变量缓存到寄存器中,以提高访问速度。但对于被
volatile
修饰的变量,编译器会知道其可能会被外部因素修改,因此会禁止这种优化,每次访问都会重新从内存中读取变量的值。 -
处理多线程环境下的共享变量: 在多线程编程中,如果多个线程共享一个变量,而且其中一个线程对该变量进行了修改,其他线程也需要立即看到这个变量的变化。在这种情况下,将共享变量声明为
volatile
可以确保每次读取该变量时都会从内存中重新获取其值,而不是使用之前的缓存值。
10.数组和指针的区别
-
内存分配方式:
- 数组在声明时需要指定其大小,内存空间在编译时就已经分配好了,大小是固定的。
- 指针只是一个存储地址的变量,它不会分配内存空间。需要通过指针指向已分配的内存区域,或者通过动态分配内存来使用。
-
使用方式:
- 数组可以直接通过下标访问其元素,数组名本身代表数组的地址。
- 指针需要通过间接寻址(解引用)来访问其所指向的内存地址中的内容。
-
可变性:
- 数组的大小在声明时就已经确定,无法改变。
- 指针可以随时指向不同的内存地址,可以动态改变其指向的位置。
作为函数参数:
- 数组作为函数参数时,实际上传递的是数组的地址,因此函数中对数组元素的修改会影响到原始数组。
- 指针作为函数参数时,可以通过传递地址或者传递指针本身来实现对函数外部变量的修改,但需要注意指针的作用域和生命周期。
11. 野指针出现的情况
什么是野指针:指向非法地址空间的指针变量。
返回局部变量的地址
定义局部指针变量时,没有置NULL
使用free是否堆区空间之后,没有置NULL
12. 指针数组和数组指针的区别
1. 定义指针数组的格式
数据类型 * 数组名[列数] = {初始值};
2. 特点:
指针数组本质是一个数组,数组每个元素的类型为指针类型(数据类型 *),
及指针数组的每个元素存储的是一个地址。
指针数组就可以看成是一个特殊的一维数组。
1. 定义数组指针的格式
数据类型 (*数组指针变量名)[列数] = 地址;
2. 特点:
1> 数组指针的本质是一个指针类型的变量; 本质是指针
2> 数组指针类型的变量指向的是有"列数"的二维数组,二维数组的行数没有要求
13. 指针函数和函数指针的区别
返回类型 * 函数名(形参列表)
{
// 函数体
不可以返回局部变量的地址
可以返回全局变量地址/可以返回static修饰的局部变量的地址
/可以返回malloc分配的堆区地址/可以返回通过形参传递的地址
}
函数指针做回调函数
返回类型 (*函数指针变量名)(形参列表);
// 将函数指针变量名当成函数名使用
// 回调函数