嵌入式八股 - C语言 二
11、字符串末位\0问题
定义字符串有多种方式,下面将具体说明:
1)末尾自动加上\0
char str1[] = "hello"; char str2[10] = "hello"; char *str3 = "hello";
2)末尾不自动加上\0
char str5[] = {'h', 'e', 'l', 'l', 'o'};
12、strlen 和 sizeof 作用和区别
strlen和sizeof都可以用来计算变量所占内存大小。
- strlen专门用来计算字符串大小,且其长度不包括末尾的'\0',计算结果是字符串的有效字符长度。
- 而sizeof可以计算任何数据类型变量所占内存大小,在计算字符串大小时其长度包括末尾的'\0',其计算结果是变量或类型占用的总内存字节数。
char str2[10] = "hello"; // strlen计算结果为5 // sizeof计算结果为10
13、数组越界有什么危害?
- 读取越界:获取无效数据(可能是随机值或其他变量数据),导致逻辑错误。
- 写入越界:
- 覆盖相邻变量 / 栈数据,破坏程序状态;
- 覆盖函数返回地址(栈溢出),导致程序崩溃或执行恶意代码;
- 破坏堆结构(动态数组越界),引发内存泄漏或崩溃。
注意:C/C++ 不检查数组越界,需手动保证访问合法性。
14、栈泄漏有什么危害
使用栈内存时,写入超出栈内存分配范围的数据,通常会导致程序崩溃或者产生不可预测的行为。
- 程序崩溃:当栈溢出时,程序可能会崩溃,并且在错误日志中显示堆栈溢出的消息
- 未定义行为:栈溢出可能会导致程序进入未定义状态,产生不可预测的行为,可能会导致程序错误、数据损坏等问题。
15、内存泄露是什么
内存泄露指在一个程序中申请动态内存而没有进行相应的释放,导致这部分内存无法被再次利用。
发生原因:
- 分配后忘记释放。比如使用
malloc或new分配了内存,但在内存使用完毕后没有使用对应的free或delete进行释放。 - 指针被覆盖。在释放内存之前,指向这块内存的指针被重新赋值,指向了新的内存地址。这样,原始内存块的地址就丢失了,程序再也无法访问和释放它。
- 异常或错误导致提前退出。如果程序因为发生异常、错误或接收到信号而提前终止,可能会跳过正常的内存释放代码,从而导致泄漏。
内存泄漏的危害:性能下降、程序崩溃、系统不稳定(本质上还是内存减少或者耗尽引起的问题)
16、指针数组与数组指针的区别
- 数组指针本质还是指针,其是指向数组的指针。比如int (*p)[10]。
- 指针数组本质还是数组,其每个元素都是指针,比如int *p[10]。
17、指针函数与函数指针的区别
- 函数指针本质还是指针,其可以指向一个函数。其定义形式如下:
/* 函数指针定义形式
* 说明;返回值类型为int;有2个函数参数且皆为int型
*
* 含义:其可以指向一个返回类型为int,且具有两个int类型的参数的函数
*/
int (*p)(int, int);
/******************* 示例如下 *******************/
int add(int a, int b) { return a + b; }
// 定义一个函数指针 p,指向 add 函数
int (*p)(int, int) = add;
// 通过指针调用函数
int result = p(3, 4);
// result 为 7
- 指针函数本质是函数,其函数返回值是指针。其定义形式如下:
int *func(int, int);
18、数组名和指针区别和联系
区别:
- 数组名表示数组第一个元素首地址,是一个地址常量;而指针则是指向地址,可以改变指向
- 数组名不能进行自增自减操作;而指针则可以
- 可以通过数组名直接获取数组数据,比如通过下标;而指针则需要解引用间接获取数据
- 在声明时数组名时分配一块连续的内存空间;而使用指针可以仅分配存储地址的空间,指向的内存可动态分配
联系:
- 在很多场景中,数组名会自动转换为一个指向其首元素的指针。
需要注意的是,将数组名和指针作为sizeof参数时,sizeof(数组名)计算的是数组大小,而sizeof(指针)计算的则是指针本身大小。
19、野指针是什么?
野指针指的是一个指向了无效、未知或不可访问内存地址的指针。
野指针的产生通常有以下几种情况:
- 声明指针时没有进行初始化
int *p; // 野指针 int *p = NULL; // 建议
- 指针指向的内存被释放后没有进行重新初始化或者赋值
这种情况下指针的也被称为悬空指针!
int *p = (int*)malloc(sizeof(int)); *p = 5; free(p); // 内存被释放 // p 仍然指向原来的地址,但已是无效内存 *p = 10; // 危险!访问已释放的内存 // 建议 free(p); // 内存被释放 p = NULL; // 再赋值为NULL
- 指针指向了已销毁的局部变量
int* get_addr() {
int num = 100;
return # // 返回局部变量的地址
}
int *p = get_addr(); // p 指向的内存已无效
printf("%d", *p); // 危险!读取到的值是随机的
- 指针越界访问
int arr;
int *p = arr;
for(int i = 0; i <= 5; i++) { // 循环条件错误,导致越界
*(p + i) = i;
}
如何有效规避野指针?
- 初始化:在声明指针时,如果暂时没有明确的指向目标,务必将其初始化为
NULL。 - 释放后置空:在使用
free()或delete释放内存后,立即将对应的指针赋值为NULL。 - 访问前检查:在解引用指针(使用
*p)之前,先判断它是否为NULL。 - 注意不返回局部变量地址:不要从函数中返回指向栈内存的指针。如果需要返回数据,应使用动态分配的内存或通过参数传递指针。
- 如果是在 C++ 中,使用智能指针
20、引用头文件时””和<>方式有什么区别
使用<>方式时编译器会优先从系统目录里面寻找头文件;而使用””方式时编译器会先从当前源文件目录寻找头文件后续再去系统目录里面寻找头文件。
| 系统目录 | 标准库、系统头文件、第三方库 |
| 先当前源文件目录后系统目录 | 项目内部的自定义头文件 |
涉及嵌入式全方面知识。根据个人学习以及面试所得,并且加上自己见解、理解记忆方法。
查看3道真题和解析