AKTK-Lab

Back

为了面试某嵌入式小厂进行刷题训练的产物,我实在不明白这种规模的厂居然还搞笔试。

运算符#

二进制位运算#

a |= 1 << n // 设置第n位
a &= ~(1 << n) // 清空第n位
a ^= 1 << n // 翻转第n位
a & (1 << n) // 第n位是否为1
if (x & 1) // 判断x是否为奇
c

十六进制位运算#

  1. 在不改变其他位的值的情况下,对某几个位设值,先对需要置值的位用 & 操作符清零,然后用 | 操作符设值,比如要改变GPIOA的状态,可以先对寄存器内的值进行清零操作
GIOA->CRL &= 0XFFFFFF0F // 将4~7位清零
GPIOA->CRL |= 0X00000040;
c
  1. 移位,例如这个操作就是将BSRR寄存器的第 pinpos 位设置为1
GPIOx->BSRR = (((uint32_t)0x01) << pinpos);
GPIOx->BSRR = 0X0030; // 直接设置值
c

第一种写法更好,这是因为可以很直接地知道这个操作置的是BSRR寄存器的哪个功能位,这样通过查看微控制器的寄存器手册就可以知道这个操作所实现的功能是什么了。而第二种写法则还需要一个转换的过程。

  1. 取反,例如定时器TIMx的SR寄存器的每一位都表示一个外设的状态,如果某个时刻想要去置某一位为0,同时保留其他位为1,则简单的方式是给寄存器直接置值,这行代码的功能是将SR寄存器的第0位设置为0
TIMx->SR = 0XFFFE;
c

可读性比较差,工程里好的写法如下,这样通过取反的方式就可以看出操作的目的就是将第0位置零

#define TIM_FLAG_Update      ((uint16_t) 0X0001)
#define TIM_FLAG_CC1         ((uint16_t) 0X0002)
TIMx->SR = (uint16_t) ~TIM_FLAG;
c

运算符优先级#

优先级运算符名称/说明结合性
1() [] -> .圆括号、数组下标、结构体成员访问从左到右
1expr++ expr--后缀自增、自减从左到右
2++expr --expr前缀自增、自减从右到左
2+ -正号、负号(一元)从右到左
2! ~逻辑非、按位取反从右到左
2*指针解引用从右到左
2&取地址从右到左
2(type)强制类型转换从右到左
2sizeof取大小从右到左
3* / %乘、除、取模从左到右
4+ -加、减从左到右
5<< >>左移、右移从左到右
6< <= > >=关系运算从左到右
7== !=相等、不等从左到右
8&按位与从左到右
9^按位异或从左到右
10|按位或从左到右
11&&逻辑与从左到右
12||逻辑或从左到右
13?:条件(三目)运算符从右到左
14= += -= *= /= %= <<= >>= &= ^= =赋值运算从右到左
15,逗号运算符
  • x=3, y=1, z=0x<=y<=z 的结果:从左到右判断,x≤y 是0,0≤z 为1
  • (x++ * 1/3) 的执行顺序:x++ 是在 后执行的,也就是 (x*1/3);x++;

sizeof 运算符#

sizeof 是C语言的一种单目操作符,并不是函数。

sizeof 以字节形式给出了其操作数的存储大小。操作数可以是一个表达式或括在括号内的类型名。操作数的存储大小由操作数的类型决定。

sizeof 的结果类型是 size_t,该类型保证能容纳实现所建立的最大对象的字节大小。

sizeof(char)          = 1;
sizeof(unsigned char) = 1;
sizeof(signed char)   = 1;
sizeof(int)            = 4;
sizeof(unsigned int)   = 4;
sizeof(short int)      = 2;
sizeof(unsigned short) = 2;
sizeof(long int)       = 4;
sizeof(unsigned long)  = 4;
sizeof(float)          = 4;
sizeof(double)         = 8;
sizeof(long double)    = 12;
c

操作数为指针的时候结果依靠编译器,一般Unix/Linux的指针字节数为4

char *p;      //Linux中
sizeof(p) = 4;
c

操作数为数组类型时结果是数组的总大小。

char a[5];
sizeof(a) = 5;
c

操作数是具体字符串/数值时会自动转化为对应类型。

sizeof(8)    = 4;  //自动转化为int类型
sizeof(8.8)  = 8;  //自动转化为double类型,注意,不是float类型
sizeof("ab") = 3   //自动转化为数组类型
c

操作数是 union 时为最大成员的大小

union u{            
    char c;
    double d;
} u;
sizeof(u) = 8;
c

操作数是 struct 时考虑边界对齐,该例中 b 要加上7个byte对齐到能够整除8

#pragma pack(n) 按照 n 字节对齐,否则按结构体里最大的成员对齐(取倍数)

struct a{             
    char b; 
    double x;
}a;
sizeof(a) = 16;
c

操作数是函数中的数组形参或函数形参,sizeof 为其指针大小

restrict 关键字(C99)#

告诉编译器该指针是访问其所指对象的唯一途径,编译器可以据此进行更激进的优化(如减少内存读取)

柔性数组(C99)#

例如 data[0] 在结构体中不占空间

typedef struct list_t{
		struct list_t *next;
		struct list_t *prev;
		char data[0];
} list_t;
c

在32bit系统中 sizeof(list_t) 大小为 8byte

*next*prev 指针类型,占4字节,data[0] 零长度数组,不占用实际空间,内存对齐,整个结构体的大小需要是最大基本类型大小(这里是4)的整数倍。

printf():格式化输出#

格式输出控制符#

  1. %:格式说明的起始符号,不可缺少。
  2. -: 有表示左对齐输出,如省略表示右对齐输出。
  3. 0:有表示指定空位填0,如省略表示指定空位不填。
  4. m.nm 指域宽,即对应的输出项在输出设备上所占的字符数。n 指精度,用于说明输出的实型数的小数位数。未指定 n 时,隐含的精度为n=6 位。针对字符输出 %m.ns:输出占 m列,但只取字符串中左端n 个字符
  5. l:对整型指long型,对实型指double型。
  6. h:用于将整型的格式字符修正为short

格式输出数据类型控制#

  1. d格式:用来输出十进制整数。%ld:输出长整型数据。%mdm 为指定的输出字段的宽度。
  2. o格式:以无符号八进制形式输出整数。对长整型可以用 %lo 格式输出。同样也可以指定字段宽度用 %mo 格式输出。
  3. x格式:以无符号十六进制形式输出整数。对长整型可以用 %lx 格式输出。同样也可以指定字段宽度用 %mx格式输出。
  4. u格式:以无符号十进制形式输出整数。对长整型可以用 %lu 格式输出。同样也可以指定字段宽度用 %mu 格式输出。
  5. c格式:输出一个字符。
  6. s格式:用来输出一个字符串
  7. f格式:用来输出实数(包括单、双精度),以小数形式输出。
  8. e格式:以指数形式输出实数。
  9. g格式:自动选 f 格式或 e 格式中较短的一种输出,且不输出无意义的零。

宏定义#

#define 标识符 字符串
c

条件编译#

在嵌入式开发过程中经常会遇到:当满足某个条件时才对一组语句进行编译,而当条件不满足时则编译另一组语句

#ifdef 标识符
程序1
#else
程序2
#endif
c

extern 外部声明#

C语言中的 extern 声明可以置于变量或函数前,以表示变量或函数的定义在别的文件中,提示编译器遇到此变量或函数时在其他文件中去寻找其定义。

可以在多个文件中进行extern声明,但是只能定义一次。而且编译器默认头文件中变量和函数的声明都是 “外部声明的”,即看做是进行了 隐式extern声明,不需要额外进添加 extern 标识符,但是为了代码的风格统一,建议加上。

typedef 类型重定义#

用于为现有的类型创建一个新的别名,用于简化变量定义或使得变量名具有更好的可读性。

struct _GPIO {
    __IO uint32_t CRL;
    __IO uint32_t CRH;
    __IO uint32_t IDR;
    ...
} GPIO_TypeDef;
GPIO_TypeDef GPIOA;
c

类型转换#

  1. 当不同数据类型进行混合运算时,较小的数据类型会自动向较大的数据类型转换
  2. 数据类型按照精度和范围从小到大的顺序为: char < int < float < double

字符串数组#

char acX[]="abc";
char acY[]={'a','b','c'};
char *szX="abc";
char *szY="abc"
c
  • 第一种为字符串常量,存储在只读数据段,内容不可修改,会自带 \0 结束符
  • 第二种可修改
  • 相同的字符串常量会被编译器优化到同一地址

Reference#

C语言 part.1 语言特性
https://astro-pure.js.org/blog/c-basic
Author Akatsuki
Published at February 13, 2026