笔者最近在找工作,因此对应聘C/C++嵌入式开发工程师容易被问到,或者经常搞不清楚的问题做一个汇总,也希望能对找工作的小伙伴起到帮助参考的作用。本篇集中于C语言方面的面试题目。
因为是自己总结的,可能会存在错误,还烦请各位读者批评指正。
一、变量内存分配
1. 一个由C/C++编译的程序占用的内存分为以下几个部分:
①栈区 —— 局部变量 —— 向低地址生长 —— 自动释放 —— 其操作方式类似于数据结构中的栈。
②堆区 —— 向高地址生长 —— 手动分配、释放的存储区 —— malloc,free —— 它与数据结构中的堆是两回事,分配方式倒是类似于链表
③全局/静态存储区static —— 全局变量,静态变量,程序运行结束后自动释放
④常量存储区const —— 常量字符串储存在这里。储存在常量区的只读不可写。程序运行结束后自动释放
⑤代码区 —— 存放函数体的二进制代码。
- 静态内存分配:编译时分配,包括:全局、静态全局、静态局部
- 动态内存分配:运行时分配:包括:栈(局部变量),堆(C语言常用到的变量被动态地分配到内存当中:malloc,calloc,realloc,free函数)
——> const修饰的全局变量也储存在常量区;
——> const修饰的局部变量依然在栈上。
int a = 0; //全局初始化区 char *p1; //全局未初始化区 main() { int b; //栈 char s[] = "abc"; //栈 char *p2; //栈 char *p3 = "123456"; //123456
int a = 0; //全局初始化区 char *p1; //全局未初始化区 main() { int b; //栈 char s[] = "abc"; //栈 char *p2; //栈 char *p3 = "123456"; //123456\0在常量区,p3在栈上。 static int c =0; //全局(静态)初始化区 p1 = (char *)malloc(10); p2 = (char *)malloc(20); //分配得来得10和20字节的区域就在堆区。 strcpy(p1, "123456"); //123456\0放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。 }
在常量区,p3在栈上。
static int c =0; //全局(静态)初始化区
p1 = (char *)malloc(10);
p2 = (char *)malloc(20); //分配得来得10和20字节的区域就在堆区。
strcpy(p1, "123456"); //123456int a = 0; //全局初始化区 char *p1; //全局未初始化区 main() { int b; //栈 char s[] = "abc"; //栈 char *p2; //栈 char *p3 = "123456"; //123456\0在常量区,p3在栈上。 static int c =0; //全局(静态)初始化区 p1 = (char *)malloc(10); p2 = (char *)malloc(20); //分配得来得10和20字节的区域就在堆区。 strcpy(p1, "123456"); //123456\0放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。 }
放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。
}
2. 存储类(内存管理):
①栈:局部变量,函数调用传参的过程。
②堆:动态存储区,需要程序员去申请释放
③数据段(data段):显式初始化仅非零的全局变量
3.static修饰的变量
(1)static修饰局部变量(静态局部变量)与普通局部变量相比:
① 静态局部变量作用域与连接属性,和普通局部变量一样
② 存储类:静态局部变量分配在data/bss段,普通局部变量在栈上
③ 生命周期:因为存储类的不同,静态局部变量生命周期变长了,直到程序结束——所以当静态局部变量离开作用域后,并没有销毁,而是仍然驻留在内存当中,只不过我们不能够再对它进行访问,直到该函数被再次调用,并且值不变。
(2)static修饰的全局变量or函数与普通的相比:
① 存储类、生命周期、作用域都一样
② 差别在于:
static修饰的全局变量的连接属性是内连接,普通的是外连接
即:static修饰的全局变量不能给文件调用——这也是静态变量和全局变量的区别。
对于局部变量来说,声明存储类型的作用是指定变量存储的区域(静态存储区或动态存储区)以及由此产生的生存期的问题
对于全局变量来说,由于都是在编译时分配内存,都存放在静态存储区,声明存储类型的作用是变量作用域的扩展问题。
4. 其他
1. 变量类型:是对数据分配存储单元的安排,包括存储单元的长度,及数据的存储形式
2. 内部函数:只能被本文件中的其他函数调用。定义内部函数时,在函数名、函数类型前加static。
外部函数:可供其他文件调用。定义外部函数时,在函数首部左端加上extern。若定义函数时省略extern,则默认为外部函数。
3. 因为A、B、C是外部变量
所以调用max函数时用不到参数传递,即在max函数中可以直接使用外部变量A、B、C的值
(这一点与局部变量有个实参传给形参的过程不同)
二、堆和栈有什么区别?(为什么又是这个)
1、堆栈空间分配区别
栈(操作系统):由操作系统(编译器)自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
堆(操作系统): 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收,分配方式倒是类似于链表。
2、堆栈缓存方式区别
栈使用的是一级缓存, 它们通常都是被调用时处于存储空间中,调用完毕立即释放。
堆则是存放在二级缓存中,生命周期由虚拟机的垃圾回收算法来决定(并不是一旦成为孤儿对象就能被回收)。所以调用这些对象的速度要相对来得低一些。
3、堆栈数据结构区别
堆(数据结构):堆可以被看成是一棵树,如:堆排序。
栈(数据结构):一种先进后出的数据结构。
三、数据结构集中问题
1. 串值的存储空间可在程序执行过程中动态分配而得。
2. 根结点是没有双亲的,所以我们约定根结点的位置域为-1.
3. 链表翻转(迭代法)
/** * Definition for singly-linked list. * struct ListNode { * int val; * struct ListNode *next; * }; */ struct ListNode* reverseList(struct ListNode* head){ if (head == NULL || head->next == NULL) return head; struct ListNode *pre = head; struct ListNode *cur = head->next; struct ListNode *tmp = head->next->next; while(cur) { tmp = cur->next; //当前位置的下一个的值给tmp先存着 cur->next = pre; //把上一个位置的值给下一个 pre = cur;//把当前位置的值给上一个 cur = tmp;//把之前下一个的值给当前位置 } head->next = NULL; return pre; }
四、指针
1. *
在不同的场景下有不同的作用:
*
可以用在指针变量的定义中,表明这是一个指针变量,以和普通变量区分开;
*
也可以在使用指针变量时,在变量前面加上,表示获取指针指向的数据,或者说表示的是指针指向的数据本身。也就是说,定义指针变量时的
*
和使用指针变量时的*
意义完全不同。以下面的语句为
int *p = &a; *p = 100;
第1行代码中
*
用来指明 p 是一个指针变量,第2行代码中*
用来获取指针指向的数据。需要注意的是,给指针变量本身赋值时不能加
*
。修改上面的语句:int *p; p = &a; *p = 100;
第2行代码中的 p 前面就不能加
*
。指针变量也可以出现在普通变量能出现的任何表达式中,例如:
int x, y, *px = &x, *py = &y; y = *px + 5; //表示把x的内容加5并赋给y,*px+5相当于(*px)+5 y = ++*px; //px的内容加上1之后赋给y,++*px相当于++(*px) y = *px++; //相当于y=(*px)++ py = px; //把一个指针的值赋给另一个指针
2. 如果已执行“p=&a;”,即指针变量p指向了整型变量a,则:
printf("%d",*p);
其作用是:以整数形式输出指针变量p所指向的变量的值,即变量a的值。
3. 当数组作为函数的参数传递时,数组就自动退化为同类型指针。
五、杂项
1. const和define的区别
1.数据类型:const修饰的变量有明确的类型,而宏没有明确的数据类型
2.安全方面:const修饰的变量会被编译器检查,而宏没有安全检查
3.内存分配:const修饰的变量只会在第一次赋值时分配内存,而宏是直接替换,每次替换后的变量都会分配内存
4.作用场所:const修饰的变量作用在编译、运行的过程中,而宏作用在预编译中
5.代码调试:const方便调试,而宏在预编译中进行所以没有办法进行调试。
—— const关键字的作用:
1. const 是定义只读变量的关键字,或者说 const 是定义常变量的关键字。
说 const 定义的是变量,但又相当于常量;说它定义的是常量,但又有变量的属性,所以叫常变量。用 const 定义常变量的方法很简单,就在通常定义变量时前面加 const 即可,如:const int a = 10;
const 和变量类型 int 可以互换位置,二者是等价的,即上条语句等价于:
int const a = 10;
那么用 const 修饰后和未修饰前有什么区别呢?它们不都等于 10 吗?、
- 用 const 定义的变量的值是不允许改变的,即不允许给它重新赋值,即使是赋相同的值也不可以。所以说它定义的是只读变量。这也就意味着必须在定义的时候就给它赋出值。
- 如果定义的时候未初始化,我们知道,对于未初始化的局部变量,程序在执行的时候会自动把一个很小的负数存放进去。这样后面再给它赋出值的话就是“改变它的值”了,即发生语法错误。
2.
const int * p1 = &i; //p1指向的值不能改变
int * const p2 =&j; //p2本身的值(即指向的值的地址)不能改变
上面定义了两个指针p1和p2。
在定义1中const限定的是*p1,即其指向空间的值不可改变,若改变其指向空间的值如*p1=20,则程序会报错;但p1的值是可以改变的,对p1重新赋值如p1=&k是没有任何问题的。
在定义2中const限定的是指针p2,若改变p2的值如p2=&k,程序将会报错;但*p2,即其所指向的值可以改变,如*p2=80是没有问题的,程序正常执行。
const常量会在内存中分配??
2. ifndef – endif 的作用:避免重定义
3. 参数传递:
三种参数传递的方式:传值、传指针、传引用
形参的存储空间是函数被调用时才分配的
- 引用是别名,指针是地址(实体)
引用一旦与某个对象绑定后就不再改变了
string str1 = "a";
string str3 = "b";
string &str2 = str1; //str2指向str1的地址
str2 = str3;
4. malloc和calloc
malloc:分配n个字节 calloc:分配n*size个字节
5. 进程和线程的区别?
进程:是执行中一段程序,即一旦程序被载入到内存中并准备执行,它就是一个进程。进程是表示资源分配的的基本概念,又是调度运行的基本单位,是系统中的并发执行的单位。
线程:单个进程中执行中每个任务就是一个线程。线程是进程中执行运算的最小单位。
一个线程只能属于一个进程,但是一个进程可以拥有多个线程。多线程处理就是允许一个进程中在同一时刻执行多个任务。
6. #include <> 和 #include” “
老生常谈的问题。
#include <> :到保存系统标准头文件的位置查找头文件。
#include” “:查找当前目录是否有指定名称的头文件,然后再从标准头文件目录中查找。
7.递归
每个递归必须至少有一个条件,其满足时递归便不再运行,即:此时不再引用自身,而是返回值退出。
for (i=2;i<40;i++)
a[i]=a[i-1]+a[i-2];
int Fbi(int i){
return Fbi(i-1)+Fbi(i-2);
}
8. 输入逗号
使用 scanf(“%d%d%d”,&a,&b,&c); 从键盘中获得任意 3 个数。在输入数据时,在两个数据之间以一个或多个空格间隔,也可以用 Enter 健、Tab 键,不能用逗号作为两个数据间的分隔符。
如果用格式输入函数 scanf(“%d,%d,%d”,&a,&b,&c) 输入数据,两个数据之间要用“,”做间隔。
9. 各种数据类型的长度
1. signed char取值范围bai是 -128 到 127 ——一个字节,2的8次方
unsigned char 取值范围是 0 到 255
2. 一个int占4个字节,sizeof就是4。在32位系统上,对任意指针求sizeof得到的结果都是4.??
10. 输出
printf("x1=%7.2f\nx2=%7.2f\n",x1,x2);
x1=%7.2f
7.2是指:宽度占7个,精确到小数点后两位(输出数据占7列,其中小数占2列)
x1= -1.00
x2= -2.00
NULL==ptr
free()
FreeRTOS任务调度方式
二分 哈希
pa、pb、pc 每次加 1,它们的地址分别增加 4、8、1,正好是 int、double、char 类型的长度;减 2 时,地址分别减少 8、16、2,正好是 int、double、char 类型长度的 2 倍。
887
1.左边界:只需要考虑相邻右元素是否存在坑
2.右边界:只需要考虑相邻左元素是否存在坑
3.中间:考虑相邻两边元素是否存在坑
最新评论