嵌入式面试/笔试C相关总结

1、存储

        单片机端编译后分为code ro rw zi几个区域,其中code是执行文件,ro(read only)只读区域,存放const修饰常量、字符串。rw(read write)存放已初始化变量。zi存放未初始化变量。编译完成后bin大小为code+ro+rw。运行时所需内存为rw+zi。

        在电脑端编译完后会分为text data bss三种,其中text为可执行程序,data为初始化过的ro+rw变量,bss为未初始化或初始化为0变量。

2、内存分配

        内存分配分为静态存储区、栈、堆三种。

可以看到静态存储区保存全局变量、常量,除此外函数内使用static修饰的变量也会储存在该区域(通常函数内变量存储在栈中,函数返回时销毁)。由此可以看到static修饰其中一个作用:使得变量存放在静态存储区,在程序运行期间均有效。虽然全局静态变量与局部静态变量在程序运行期间均存活,但局部静态变量仅在其作用域可访问。

堆由动态内存相关函数进行操作,如malloc free。栈由编译器分配,主要是调用函数时的参数、变量以及保护现场压栈使用。

3、运算符优先级

序号运算符结合方向
1(最高)() [] -> .左到右
2* & sizeof() (强转) ++ -- ! ~ -(负号)右到左
3* / %左到右
4+ - 左到右
5>> <<左到右
6>= <= < >左到右
7!= ==左到右
8&左到右
9^左到右
10|左到右
11&&左到右
12||左到右
13?:右到左
14

= += -= *= /= %= >>= <<= 

&= ^= |=

右到左
15(最低),左到右

由上表可知,将q复制到p数组可以表示为:*p++=*q++,*优先级高,先取到对应q数组的值,然后两个++都是在后面,该行运算完后执行++。可以分为*p=*q;p++,q++;两行。

4、函数调用时的参数运算

        例如:int a = 1,b=2,c=3;printf("%d,%d,%d\n",a+b+c,b+=2,c*=2);本该是不同编译器结果不同,但是尝试了g++ msvc都是先计算c,再计算b,最后得到a+b+c是经过赋值以后的b和c参与计算而不是6。

5、static

        static作用分为三种:局部变量,全局变量,函数。

修饰全部变量和函数作用一样:仅在模块/文件内使用。

修饰局部变量:将其存放到静态存储区,不会随着函数结束销毁。从而导致其值不会每次都重新初始化,而是初始化一次后续每次都是上一次的值。其他文件内函数无法访问,这是局部变量的特点。

6、大小端

        大端是常见的理解形式,高字节放在低地址,0x12345678中0x12是最高字节,0x78是最低字节。而小端中高字节在低地址。具体区分方法参考https://blog.csdn.net/qq_29144629/article/details/104986767

7、指针

        指针与数组相关参考c语言指针与数组_勿忘初心,方得始终。的博客-CSDN博客

8、结构体

        结构体定义方式:

typedfe struct _type_def{
    int val;
    struct _type_def *ptr;
}type_def;

后续可以使用type_def A;或者struct _type_def A;的形式定义变量。注意二者区别以及如果使用链表形式时需要在结构体里面使用struct _type_def去定义,这样不会报错,如果使用type_def去定义会有一些编译器报错。

        结构体大小不是简单的累加和,而是最长数据类型的整数倍,所以要尽可能把短的数据放在一起,而不是各自占用一个新空间。例如:

typedef struct{
char A;
int B;
char C;
}my_typedef;

所占用空间是int的三倍,具体取决于int的长度。而不是两个char加一个int的大小。如果定义为:

typedef struct{
char A;
char C;
int B;
}my_typedef;

只占用两倍int空间,同样,如果在C与B中间再加上一个char D;其结果还是两倍int空间。

如果想要结构体占用其数据类型对应的大小个字节需要使用attribute将其强制1字节对其。具体参考:C语言__attribute__学习记录___attribute__ (( section可以把变量放到多个段中吗_勿忘初心,方得始终。的博客-CSDN博客

9、位域

        位域用于不需要太长数据类型对象,例如1个或者几个bit。通常与结构体结合使用:(可以使用空位域)

typedef struct{
        unsigned int F1_Bit:2;
        unsigned int :1;
        unsigned int F2_Bit:10;
}Bit_typedef;

10、联合体

        联合体与结构体定义方式类似,形如:

typedef union _learn
{
    int a[7];
    char b;
    double c;
}learn;

其中所有变量共用同一块内存,在dsp配置中比较常见,可以直接配置寄存器,或者对不同功能的不同位进行配置。其占用空间对其方式与结构体类似,需要与最长数据类型对其,也就是整数倍。上例中a占用28个字节(32位系统),但是double位8字节,28不能整除所以需要往上取整,结果占用32个字节。

通过联合体与结构体加上位域可以实现对寄存器全读写或者部分读写:

typedef struct{
        unsigned int F1_Bit:2;
        unsigned int F2_Bit:10;
}Bit_typedef;


typedef union _Reg_t
{
    unsigned int Reg;
    Bit_typedef Bit;

}Reg_t;

可以通过Reg_t regA;regA.Reg = 0X12345678;对整个4字节赋值,或者通过regA.Bit.F1_Bit进行读写。

12、内联函数

        内联函数的优点是可以提高代码的执行效率,减少函数调用的开销。此外,内联函数也可以进行类型检查,避免了宏定义的类型错误。但是,内联函数也有一些缺点。首先,内联函数的代码会被复制到调用它的地方,如果内联函数的代码很长,会导致可执行文件变大,影响程序的运行效率。其次,内联函数的定义必须放在头文件中,这会增加头文件的大小,降低编译速度。最后,内联函数的使用必须谨慎,过度使用内联函数会导致程序的可维护性变差。

13、预处理

        预处理是指在程序代码实际编译之前,对代码进行一系列的处理操作。预处理器是一种能够读取代码文件并执行预处理操作的程序,它可以执行宏定义替换、文件包含、条件编译等操作,从而提高程序代码的效率和可读性。

14、转义字符串

       当\出现在字符串中时如果后面有x或则0会对后续数字进行转义,如"\x412","\098",\x是获取16进制值,\取8进制值,且\后面仅仅有三位为转义,如"\01234"结果是{10,‘2’,‘3’,‘4’,0}。但是\x就没有该限制"\x132178471",后面多长都可以,但是会被截断最后两个16进制字符,且不能超过0x7F,上式结果为{0X71,0},如果出现非0-F字符会停止转义,然后添加该字符的ascii值。不能一个字符都没有就出现非0-f字符,如"\xz",编译报错,而"\x1z"为{1,'z',0};

15、free函数

        malloc成功后会在返回指针前面有一个区域存放该动态内存的信息,其中就有其大小,所以free时可以通过该偏移获取到其大小。但是这个偏移与环境有关,在msvc上是地址是(char*)p-12,也就是返回地址前面第12个字节。但是同样的程序g++结果又不一样。所以只能知道的是返回地址的一个偏移上有一块动态内存信息区域,上面有记录该内存大小,具体位置和编译器有关。

        另外,数据结构里面的堆和内存中的堆基本是没有任何关系。不能认为内存里面的堆是数据结构里面树的结构。二者除了名字一样,没有关系。

16、锁

        常见的锁有互斥锁、自旋锁、读写锁。

        互斥锁用于独占式访问共享资源。如果加锁失败会进入阻塞状态(由内核实现),不占用CPU。

        自旋锁实际上是while(1);然后在循环里面一直轮询等待条件是否满足,由于不释放CPU,所以常见于多核或者抢占式操作系统。

        当写锁没有被线程持有时,多个线程能够并发地持有读锁,这大大提高了共享资源的访问效率,因为读锁是用于读取共享资源的场景,所以多个线程同时持有读锁也不会破坏共享资源的数据。但是,一旦写锁被线程持有后,读线程的获取读锁的操作会被阻塞,而且其他写线程的获取写锁的操作也会被阻塞(来自:https://baijiahao.baidu.com/s?id=1678252166115910894&wfr=spider&for=pc)。

17、其他小细节

!a&&(b++),a不为0时b不会执行;或条件同理,第一个满足后不去计算后续条件。

sizeof(a++);a不会增加;

"\0" 占用两个字节,常字符串末尾一定会有一个\0。strlen("\0") = 0

char a[] = "Hello",a[0] = '3';正确

char *a = "Hello",a[0] = '3';错误

前者是分配了内存,然后把字符串赋值过去,后者是指针指向只读区域字符串,不可修改只读区域。

    int i = 1;    printf("%d\n",(++i)/(i--));结果为0,++i计算完后i=2,i--后为1,但是i--返回-之前的也就是2,这时++i返回的是i引用,也就是1.1/2=0。(重点在理解引用上,++i不是计算两次,而是引用会随着i变化变化,i--返回的是临时变量,所以不会有问题。)

volatile 预处理起作用,extern 链接时候起作用。

printf()返回打印字符个数,包括/n之类的转义字符。

外部定义变量与余部变量重名时想使用时候外部变量可以用{}包括以后加上extern int a;{}为其作用域。

{

        extern int a

}

栈使用的是一级缓存, 它们通常都是被调用时处于存储空间中,调用完毕立即 释放;堆则是存放在二级缓存中,速度要慢些。

压栈顺序是从右往左,并且内存中栈是由高向低扩展,所以先入栈的是右边并且地址是高位比如 printf()函数,也都是先打印最右边.

sizeof(函数)长度为1,sizeof(&函数)为计算机位数对应字节。

判断变量是否有符号:#define isunsigneg(a)         ((a>=0)&&((a=~a)>=0))

判断类型是否有符号:#define isunsigneg(type)     ((type)(0-1)>0)

c语言中栈的作用

1、用来保存临时变量,临时变量包括函数参数,函数内部定义的临时变量

2.多线程编程的基础就是栈,栈是缩写词编程的及时,每个线程多最少有自 己的专属的栈,用来保存本线程运行时各个函数的临时变量

#define与const

  1. #define是文本替换,在预编译期间起作用,不分配内存,存在于代码段;const生效于编译阶段,保存在数据段,在堆栈中分配空间。
  2. #define不可调试,const可以调试
  3. #define定义的为常量,const定义只读本质还是变量,c中不能初始化数组大小(c++中constexpr可以)
  4. #define没有数据检查,const会数据检查。

#define与inline

  1. #define是文本替换,不是函数,内联函数也会插入到对应位置,但不是一定插入,需要编译器判断。
  2. #define不会进行语法检查,内联函数会进行语法和参数检查。

#define与typedef

  1.  #define机械性替换,typedef 会进行语法检查。
  2. #define定义多个指针会导致只有第一个变量是指针,typedef可以定义多个指针。

虚拟内存:实现可用内存大于实际物理内存,进程隔离,实现内存共享。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值