引言
在嵌入式系统开发的面试中,常常会考察候选人对 C 语言基础知识的掌握程度。以下将详细分析几道常见的嵌入式面试题,包括解题步骤、涉及的知识点以及相关拓展。
题目 1
main() {
unsigned char z=0;
unsigned char x=100;
unsigned char y=10;
z = (~x)*(x|y);
printf(" %d\n", z);
}
解题步骤
- 计算
~x
:x = 100
,其 8 位二进制表示为01100100
。- 按位取反运算符
~
对x
的每一位进行取反操作,得到10011011
。在unsigned char
类型下,这个二进制数对应的十进制值为155
。
- 计算
x | y
:x
的二进制是01100100
,y = 10
,二进制为00001010
。- 按位或运算符
|
会对两个操作数的对应位进行逻辑或运算,即只要对应位中有一个为1
,结果位就为1
。所以x | y
的结果是01101110
,对应的十进制值是110
。
- 计算
155 * 110
并处理溢出:- 先计算
155 * 110 = 17050
。 - 由于
z
是unsigned char
类型,它只能表示 8 位数据,取值范围是0
到255
。当计算结果超出这个范围时,就会发生溢出。处理方法是对结果进行取模运算,即17050 % 256 = 154
。所以最终z
的值为154
。
- 先计算
涉及知识点
按位运算
按位运算是对二进制位进行操作的运算,常见的按位运算符有:
- 按位取反
~
:对操作数的每一位取反,0
变为1
,1
变为0
。例如,~01100100
得到10011011
。 - 按位或
|
:对两个操作数的对应位进行逻辑或运算,只要有一个位为1
,结果位就为1
。如01100100 | 00001010
得到01101110
。 - 按位与
&
:对两个操作数的对应位进行逻辑与运算,只有当两个位都为1
时,结果位才为1
。例如,01100100 & 00001010
得到00000000
。 - 按位异或
^
:对两个操作数的对应位进行异或运算,相同为0
,不同为1
。如01100100 ^ 00001010
得到01101110
。
数据类型转换
在 C 语言中,不同数据类型之间可能会发生转换。分为隐式转换和显式转换:
- 隐式转换:在表达式中,不同类型的数据进行运算时,编译器会自动将它们转换为相同的类型。例如,在
(~x)*(x|y)
中,~x
和x|y
的结果都是unsigned char
类型,它们相乘时可能会先隐式转换为int
类型进行计算。 - 显式转换:也称为强制类型转换,通过
(类型名)表达式
的形式进行。例如,(int)3.14
会将3.14
强制转换为int
类型,结果为3
。
无符号字符型溢出
unsigned char
类型是无符号的 8 位整数,其取值范围是 0
到 255
。当计算结果超出这个范围时,就会发生溢出。溢出的处理方式是对结果进行取模运算,模为 256
。例如,256
溢出后变为 0
,257
溢出后变为 1
等。
拓展
按位运算在嵌入式系统中非常有用,常用于寄存器操作、位掩码等。例如,通过按位与和按位或操作可以设置或清除寄存器的某一位。数据类型转换在处理不同精度的数据时也很常见,但要注意隐式转换可能会导致数据丢失。无符号类型溢出在一些需要循环计数的场景中可以利用,例如使用 unsigned char
作为计数器,当计数到 255
后会自动回到 0
。
题目 2
int sum(int a) {
int c=0;
static int b=3;
c+=1;
b+=2;
return(a+b+c);
}
void main() {
int i;
int a=2;
for(i=0;i<5;i++) {
printf("%d,", sum(a));
}
}
解题步骤
- 分析
c
和b
的特性:c
是局部变量,每次调用sum
函数时,c
都会被重新初始化为0
,然后执行c += 1
,所以c
的值始终为1
。b
是静态变量,在函数第一次调用时被初始化为3
。静态变量的特点是在程序的整个生命周期内只初始化一次,并且会保留上一次调用结束时的值。每次调用sum
函数时,b
都会执行b += 2
。
- 五次调用
sum(a)
:- 第一次调用:
b = 3 + 2 = 5
,c = 1
,a = 2
,所以return 2 + 5 + 1 = 8
。 - 第二次调用:
b = 5 + 2 = 7
,c = 1
,a = 2
,return 2 + 7 + 1 = 10
。 - 第三次调用:
b = 7 + 2 = 9
,c = 1
,a = 2
,return 2 + 9 + 1 = 12
。 - 第四次调用:
b = 9 + 2 = 11
,c = 1
,a = 2
,return 2 + 11 + 1 = 14
。 - 第五次调用:
b = 11 + 2 = 13
,c = 1
,a = 2
,return 2 + 13 + 1 = 16
。
- 第一次调用:
涉及知识点
局部变量
局部变量是在函数内部定义的变量,其作用域仅限于定义它的函数内部。每次调用函数时,局部变量都会被重新创建和初始化。例如,c
就是一个局部变量,每次调用 sum
函数时,c
都会被初始化为 0
。
静态变量
静态变量使用 static
关键字修饰,在函数内部定义的静态变量在程序的整个生命周期内只初始化一次,并且会保留上一次调用结束时的值。静态变量的存储位置在静态存储区,而不是栈上。例如,b
就是一个静态变量,第一次调用 sum
函数时初始化为 3
,后续调用会保留上一次的值并继续进行操作。
拓展
静态变量在需要保留函数调用状态的场景中非常有用,例如实现计数器、缓存等。但要注意,过多使用静态变量可能会导致程序的可维护性和可测试性下降,因为静态变量的状态在整个程序生命周期内都存在,可能会影响其他函数的执行结果。
题目 3
char str[] = "Hello";
char *p = str;
int n = 10;
printf("%d, %d, %d,", sizeof(str), sizeof(p), sizeof(n));
解题步骤
- 计算
sizeof(str)
:str
是一个字符数组,用来存储字符串"Hello"
。在 C 语言中,字符串是以'\0'
结尾的字符序列,所以"Hello"
实际上包含5
个字母和1
个结束符'\0'
,总共6
个字符。sizeof
运算符返回的是数组占用的内存字节数,所以sizeof(str)
的结果是6
。
- 计算
sizeof(p)
:p
是一个字符指针,它指向str
数组的首地址。在 32 位系统中,指针的大小通常是4
字节;在 64 位系统中,指针的大小通常是8
字节。这里假设是 32 位系统,所以sizeof(p)
的结果是4
。
- 计算
sizeof(n)
:n
是一个int
类型的变量,在大多数系统中,int
类型通常占用4
字节,所以sizeof(n)
的结果是4
。
涉及知识点
sizeof
运算符
sizeof
是一个 C 语言的运算符,用于计算数据类型或变量所占用的内存字节数。它的使用方式有两种:sizeof(类型名)
或 sizeof(表达式)
。例如,sizeof(int)
返回 int
类型占用的字节数,sizeof(str)
返回数组 str
占用的字节数。
数组和指针
- 数组:数组是一组相同类型的数据的集合,在内存中是连续存储的。数组名可以看作是指向数组首元素的常量指针,但它和指针还是有区别的。例如,
sizeof
对数组操作时返回的是整个数组占用的内存大小,而不是指针的大小。 - 指针:指针是一个变量,它存储的是内存地址。指针的大小取决于系统的位数,32 位系统中指针通常为
4
字节,64 位系统中指针通常为8
字节。
拓展
sizeof
运算符在动态内存分配、数组操作等方面非常有用。例如,在使用 malloc
函数分配内存时,可以使用 sizeof
来确定需要分配的内存大小。在处理不同大小的数据类型时,要注意 sizeof
的返回值可能会因系统而异。同时,要区分数组和指针的不同,避免在使用时出现混淆。
总结
通过对这几道嵌入式面试题的分析,我们可以看到嵌入式开发中对 C 语言基础知识的要求较高。掌握按位运算、数据类型转换、变量存储类型、sizeof
运算符等知识点,对于解决实际问题和应对面试都非常重要。希望大家通过学习这些内容,能够在嵌入式开发的道路上取得更好的成绩。