笔者最近在找工作,因此对应聘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"); //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所指向的"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. *在不同的场景下有不同的作用

*可以用在指针变量的定义中,表明这是一个指针变量,以和普通变量区分开;

*也可以在使用指针变量时,在变量前面加上,表示获取指针指向的数据,或者说表示的是指针指向的数据本身。

也就是说,定义指针变量时的*使用指针变量时的*意义完全不同。以下面的语句为

  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.中间:考虑相邻两边元素是否存在坑