C语言相关知识点
预处理器(Processor)
用预处理指令#define 声明一个常数,用以表明1年中有多少秒(忽略闰年问题)
#define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL
我在这想看到几件事情:
1) #define 语法的基本知识(例如:不能以分号结束,括号的使用,等等)
2)懂得预处理器将为你计算常数表达式的值,因此,直接写出你是如何计算一年中有多少秒而不是计算出实际的值,是更清晰而没有代价的。
3) 意识到这个表达式将使一个16位机的整型数溢出-因此要用到长整型符号L,告诉编译器这个常数是的长整型数。
4) 如果你在你的表达式中用到UL(表示无符号长整型),那么你有了一个好的起点。记住,第一印象很重要。
写一个"标准"宏MIN ,这个宏输入两个参数并返回较小的一个。
#define MIN(A,B) ((A) <= (B) ? (A) : (B))
这个测试是为下面的目的而设的:
1) 标识#define在宏中应用的基本知识。这是很重要的。因为在嵌入(inline)操作符变为标准C的一部分之前,宏是方便产生嵌入代码的唯一方法,对于嵌入式系统来说,为了能达到要求的性能,嵌入代码经常是必须的方法。
2)三重条件操作符的知识。这个操作符存在C语言中的原因是它使得编译器能产生比if-then-else更优化的代码,了解这个用法是很重要的。
3) 懂得在宏中小心地把参数用括号括起来
4) 我也用这个问题开始讨论宏的副作用,例如:当你写下面的代码时会发生什么事?
least = MIN(*p++, b);
死循环(Infinite loops)
嵌入式系统中经常要用到无限循环,你怎么样用C编写死循环呢?
这个问题用几个解决方案。我首选的方案是:
while(1) { } 一些程序员更喜欢如下方案: for(;;) { }
这个实现方式让我为难,因为这个语法没有确切表达到底怎么回事。如果一个应试者给出这个作为方案,我将用这个作为一个机会去探究他们这样做的基本原理。如果他们的基本答案是:"我被教着这样做,但从没有想到过为什么。"这会给我留下一个坏印象。
第三个方案是用 goto
Loop:
...
goto Loop;
应试者如给出上面的方案,这说明或者他是一个汇编语言程序员(这也许是好事)或者他是一个想进入新领域的BASIC/FORTRAN程序员。
sizeof与strlen
区别一:sizeof是运算符,在编译时即计算好了;而strlen是函数,要在运行时才能计算。
区别二:strlen 是一个函数,它用来计算指定字符串 str 的长度,但不包括结束字符(即 null 字符)。
sizeof 是一个单目运算符,而不是一个函数。与函数 strlen 不同,它的参数可以是数组、指针、类型、对象、函数等
char sArr[] = "ILOVEC";
printf("sArr的长度=%d\n", strlen(sArr)); strlen不包括结束字符(即 null 字符)
printf("sArr的长度=%d\n", sizeof(sArr)); sizeof 包括结束字符 null
C函数相关问题
在ARM系统中,函数调用的时候,参数是通过哪种方式传递的?
参数<=4时候,通过R0~R3传递,>4的通过压栈方式传递
中断(interrupt,如键盘中断)与异常(exception,如除零异常)有何区别?
异常:在产生时必须考虑与处理器的时钟同步,实践上,异常也称为同步中断。在处理器执行到由于编程失误而导致的错误指令时,或者在执行期间出现特殊情况(如缺页),必须靠内核处理的时候,处理器就会产生一个异常。
所谓中断应该是指外部硬件产生的一个电信号,从cpu的中断引脚进入,打断cpu当前的运行;
所谓异常,是指软件运行中发生了一些必须作出处理的事件,cpu自动产生一个陷入来打断当前运行,转入异常处理流程。
优先级反转问题在嵌入式系统中的问题
a) 首先请解释优先级反转问题
b) 很多RTOS提供优先级继承策略(Priority inheritance)和优先级天花板策略(Priority ceilings)用来解决优先级反转问题,请讨论这两种策略。
优先级翻转是指高优先级任务通过信号量机制访问共享资源时,该信号量被一个低优先级任务占有,因此造成高优先级任务被阻塞,实时性难以保证。
优先级继承策略(Priority inheritance):继承现有被阻塞任务的最高优先级作为其优先级,任务退出临界区,恢复初始优先级。
优先级天花板策略(Priority ceilings):控制访问临界资源的信号量的优先级天花板。
优先级继承策略对任务执行流程的影响相对教小,因为只有当高优先级任务申请已被低优先级任务占有的临界资源
这一事实发生时,才抬升低优先级任务的优先级。
数据声明(Data declarations)
用变量a给出下面的定义
a) 一个整型数(An integer)
b)一个指向整型数的指针(A pointer to an integer)
c)一个指向指针的的指针,它指向的指针是指向一个整型数(A pointer to a pointer to an integer)
d)一个有10个整型数的数组( An array of 10 integers)
e) 一个有10个指针的数组,该指针是指向一个整型数的。(An array of 10 pointers to integers)
f) 一个指向有10个整型数数组的指针( A pointer to an array of 10 integers)
g) 一个指向函数的指针,该函数有一个整型参数并返回一个整型数(A pointer to a function that takes an integer as an argument and returns an integer)
h) 一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数( An array of ten pointers to functions that take an integer argument and return an integer )
答案是:
int a; // An integer
b) int *a; // A pointer to an integer
c) int **a; // A pointer to a pointer to an integer
d) int a[10]; // An array of 10 integers
e) int *a[10]; // An array of 10 pointers to integers
f) int (*a)[10]; // A pointer to an array of 10 integers
g) int (*a)(int); // A pointer to a function a that takes an integer argument and returns an integer
h) int (*a[10])(int); // An array of 10 pointers to functions that take an integer argument and return an integer
人 们经常声称这里有几个问题是那种要翻一下书才能回答的问题,我同意这种说法。当我写这篇文章时,为了确定语法的正确性,我的确查了一下书。但是当我被面试的时候,我期望被问到这个问题(或者相近的问题)。因为在被面试的这段时间里,我确定我知道这个问题的答案。应试者如果不知道所有的答案(或至少大部分答 案),那么也就没有为这次面试做准备,如果该面试者没有为这次面试做准备,那么他又能为什么出准备呢?
Static
关键字static的作用是什么?
static函数的三个作用
- 函数内部static变量
- 函数外部static变量
- static函数
1、函数内部的static变量,关键在于生命周期持久,他的值不会随着函数调用的结束而消失,下一次调用时,static变量的值,还保留着上次调用后的内容。
2/3、函数外部的static变量,以及static函数,关键在于私有性,它们只属于当前文件,其它文件看不到他们。
函数外部static关键字
全局变量定义在函数体外部,在全局数据区分配存储空间,且编译器会自动对其初始化。
普通全局变量对整个工程可见,其他文件可以使用extern外部声明后直接使用。也就是说其他文件不能再定义一个与其相同名字的变量了(否则编译器会认为它们是同一个变量)。
静态全局变量仅对当前文件可见,其他文件不可访问,其他文件可以定义与其同名的变量,两者互不影响。
函数用static修饰
函数的使用方式与全局变量类似,在函数的返回类型前加上static,就是静态函数。其特性如下:
静态函数只能在声明它的文件中可见,其他文件不能引用该函数
不同的文件可以使用相同名字的静态函数,互不影响
非静态函数可以在另一个文件中直接引用,甚至不必使用extern声明
const修饰符
7、关键字const有什么作用?
1)const修饰普通变量
const int a = 10;
int const a = 10;
这两种写法是等价的
用 const 修饰的变量,无论是全局变量还是局部变量,生存周期都是程序运行的整个过程。
2)const修饰指针变量
const int *p //p本身不是const的,而p指向的变量是const的
int const *p //p本身不是const的,而p指向的变量是const的
int * const p; //p 本身是 const 的,而 p 指向的变量不是 const 的
const int * const p; //p 本身是 const 的,而 p 指向的变量也是 const 的
volatile关键字
1). 并行设备的硬件寄存器(如:状态寄存器)
2). 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
3). 多线程应用中被几个任务共享的变量
short flag; void test() { do1(); while(flag==1); { do2(); } }
变量flag的值由别的程序更改,这个程序可能是某个硬件中断服务程序。
产生中断时,在中断服务程序中改变flag的值,但是,编译器并不知道flag的值会被别的程序修改,因为在它进行优化的时候,可能会把flag的值先读入某个寄存器,然后等待那个寄存器变为1。如果不幸进行了这样的优化,那么while循环就变成了死循环,因为寄存器的内容不可能被中断服务程序修改。为了让程序每次都读取真正flag变量的值,就需要定义为如下形式:
volatile short flag;
一个参数既可以是const还可以是volatile吗?解释为什么?
答:可以。一个例子是只读的状态寄存器。它是volatile 因为它可能被意想不到地改变。它是const 因为程序不应该试图去修改它。
一个指针可以是volatile吗?解释为什么?
答:可以。尽管这并不常见。一个例子是当一个中断服务子程序修改一个指向buffer的指针时。
加了volatile之后, 直接从内存读值,而不是从寄存器读值。
Union与struct的区别。
1.在存储多个成员信息时,编译器会自动给struct每个成员分配存储空间,struct 可以存储多个成员信息,而Union每个成员会用同一个存储空间,只能存储最后一个成员的信息。
2.都是由多个不同的数据类型成员组成,但在任何同一时刻,Union只存放了一个被先选中的成员,而结构体的所有成员都存在。
3.对于Union的不同成员赋值,将会对其他成员重写,原来成员的值就不存在了,而对于struct 的不同成员赋值 是互不影响的。
堆与栈的区别
Heap是堆,Stack是栈。
(1)栈的空间由操作系统自动分配和回收,而堆上的空间由程序员申请和释放。
(2)栈的空间大小较小,而堆的空间较大。
(3)栈的地址空间往低地址方向生长,而堆向高地址方向生长。
(4)栈的存取效率更高。程序在编译期间对变量和函数的内存分配都在栈上,
且程序运行过程中对函数调用中参数的内存分配也是在栈上。
位操作
1、位与 &与&& 逻辑与
位与时,两个操作数是按照二进制位彼此对应位相位与的
逻辑与,两个操作数为整体来进行相与的
eg. 0xAA & 0xF0 = 0xA0
0xAA && 0xF0 = 1
2、位或 |与|| 逻辑或
“位或”与“逻辑或”的区别,同“位与”和“逻辑与”的区别
3、位异或 ^
位异或真值表:1^1= 0、0^0=0、1^0=1、0^1=1
位与、位或、位异或特点总结:
位与:与1位与无变化、与0位与变成0
位或:与1位或变成1、与0位或无变化
位异或:与1位或会取反、与0位或无变化
根据上面的三个特点:
特定位清零用&、特定位置1用|、特定位取反用^
回顾:要置 1 用|, 要清零用&,要取反用^, ~和<< >>用来构建特定二进制数。
1、给定一个整型数 a,设置 a 的 bit3,保证其他位不变。
a |= (1<<3)
2、给定一个整形数 a,设置 a 的 bit3~bit7,保持其他位不变。
a |= (0x1f<<3);
3、给定一个整型数 a,清除 a 的 bit15,保证其他位不变。
a = a & (~(1<<15)); 或者 a &= (~(1<<15));
4、给定一个整形数 a,清除 a 的 bit15~bit23,保持其他位不变。
a = a & (~(0x1ff<<15)); 或者 a &= (~(0x1ff<<15));
5、给定一个整形数 a,取出 a 的 bit3~bit8。
思路:
第一步:先将这个数 bit3~bit8 不变,其余位全部清零。
第二步,再将其右移 3 位得到结果。
第三步,想明白了上面的 2 步算法,再将其转为 C 语言实现即可。
a &= (0x3f<<3);
a >>= 3;26
6、用 C 语言给一个寄存器的 bit7~bit17 赋值 937(其余位不受影响)。
关键点:第一,不能影响其他位;第二,你并不知道原来 bit7~bit17 中装的值。
思路:第一步,先将 bit7~bit17 全部清零,当然不能影响其他位。
第二步,再将 937 写入 bit7~bit17 即可,当然不能影响其他位。
a &= ~(0x7ff<<7);
a |= (937<<7);
4.2.5.位运算实战演练 2
7、用 C 语言将一个寄存器的 bit7~bit17 中的值加 17(其余位不受影响)。
关键点:不知道原来的值是多少
思路:第一步,先读出原来 bit7~bit17 的值
第二步,给这个值加 17
第三步,将 bit7~bit17 清零
第四步,将第二步算出来的值写入 bit7~bit17
8、用 C 语言给一个寄存器的 bit7~bit17 赋值 937,同时给 bit21~bit25 赋值 17.
思路: 4.2.4.6 的升级版,两倍的 4.2.4.6 中的代码即可解决。
分析:这样做也可以,但是效果不够高,我们有更优的解法就是合两步为一步。
中断(Interrupts)
中断是嵌入式系统中重要的组成部分,这导致了很多编译开发商提供一种扩展—让标准C支持中断。具代表事实是,产生了一个新的关键字 __interrupt。下面的代码就使用了__interrupt关键字去定义了一个中断服务子程序(ISR),请评论一下这段代码。
__interrupt double compute_area (double radius) { double area = PI * radius * radius; printf("\nArea = %f", area); return area; }
这个函数有太多的错误了,以至让人不知从何说起了:
1)ISR 不能返回一个值。如果你不懂这个,那么你不会被雇用的。
2) ISR 不能传递参数。如果你没有看到这一点,你被雇用的机会等同第一项。
3) 在许多的处理器/编译器中,浮点一般都是不可重入的。有些处理器/编译器需要让额处的寄存器入栈,有些处理器/编译器就是不允许在ISR中做浮点运算。此外,ISR应该是短而有效率的,在ISR中做浮点运算是不明智的。
4) 与第三点一脉相承,printf()经常有重入和性能上的问题。如果你丢掉了第三和第四点,我不会太为难你的。不用说,如果你能得到后两点,那么你的被雇用前景越来越光明了。
代码例子(Code examples)
下面的代码输出是什么,为什么?
void foo(void) { unsigned int a = 6; int b = -20; (a+b > 6) ? puts("> 6") : puts("<= 6"); }
这个问题测试你是否懂得C语言中的整数自动转换原则,我发现有些开发者懂得极少这些东西。不管如何,这无符号整型问题的答案是输出是 ">6"。原因是当表达式中存在有符号类型和无符号类型时所有的操作数都自动转换为无符号类型。因此-20变成了一个非常大的正整数,所以该表达式 计算出的结果大于6。这一点对于应当频繁用到无符号数据类型的嵌入式系统来说是丰常重要的。如果你答错了这个问题,你也就到了得不到这份工作的边缘。
注意:当表达式中存在有符号类型和无符号类型时所有的操作数都将自动转换为无符号类型。
Typedef
Typedef 在C语言中频繁用以声明一个已经存在的数据类型的同义字。也可以用预处理器做类似的事。例如,思考一下下面的例子:
#define dPS struct s *
typedef struct s * tPS;
以上两种情况的意图都是要定义dPS 和 tPS 作为一个指向结构s指针。哪种方法更好呢?(如果有的话)为什么?
这是一个非常微妙的问题,任何人答对这个问题(正当的原因)是应当被恭喜的。答案是:typedef更好。思考下面的例子:
dPS p1,p2;
tPS p3,p4;
第一个扩展为
struct s * p1, p2;
.
上面的代码定义p1为一个指向结构的指,p2为一个实际的结构,这也许不是你想要的。第二个例子正确地定义了p3 和p4 两个指针。
晦涩的语法
C语言同意一些令人震惊的结构,下面的结构是合法的吗,如果是它做些什么?
int a = 5, b = 7, c;
c = a+++b;
这个问题将做为这个测验的一个愉快的结尾。不管你相不相信,上面的例子是完全合乎语法的。问题是编译器如何处理它?水平不高的编译作者实际上会争论这个问题,根据最处理原则,编译器应当能处理尽可能所有合法的用法。因此,上面的代码被处理成:
c = a++ + b;
因此, 这段代码持行后a = 6, b = 7, c = 12。
如果你知道答案,或猜出正确答案,做得好。如果你不知道答案,我也不把这个当作问题。我发现这个问题的最大好处是这是一个关于代码编写风格,代码的可读性,代码的可修改性的好的话题。
大端模式与小端模式(big-endial & little endian)
小端模式:低位字节放在内存的低地址中;高位字节放在内存的高地址中
所以在小端模式下:0x12345678在内存中的存放位置为:
0x80000000 78
0x80000001 56
0x80000002 34
0x80000003 12
大端模式与小端模式相反
container_of宏与offset_of宏
offset_of宏作用:计算结构体中每个成员的偏移量
// TYPE是结构体类型,MEMBER是结构体中一个元素的元素名 // 这个宏返回的是member元素相对于整个结构体变量的首地址的偏移量,类型是int #define offsetof(TYPE, MEMBER) ((int) &((TYPE *)0)->MEMBER)
container_of宏
// ptr是指向结构体元素member的指针,type是结构体类型,member是结构体中一个元素的元素名 // 这个宏返回的就是指向整个结构体变量的指针,类型是(type *) #define container_of(ptr, type, member) ({ \ const typeof(((type *)0)->member) * __mptr = (ptr); \ (type *)((char *)__mptr - offsetof(type, member)); })
bzero与memset
1、bzero()
函数原型:extern void bzero(void *s, int n)
头文件:<string.h>
功能:置字节字符串s的前n个字节为零且包括‘\0’
说明:无返回值
eg.
char a[10];
bzero(a, sizeof(char)*10)
memset(a, 0, sizeof(char)*10)
2、memset()
函数原型:extern void *memset(void *buffer, int c, int count)
头文件:<string.h>
功能:把buffer所指内存区域的前count个字节设置成c的值。