c/c++
1.用预处理指令#define声名一个常数,用以表明1年中有多少秒?
#define SEC_PER_YEAR (60*60*24*365)UL
解答:用#define声名一个变量,使用常量表达式来表示一年有多少秒,很直观。因为常表达式的值为无符号长整型,所以用符号UL。
2.写一个“标准”宏MIN,这个宏输入两个参数并返回较小的一个。
#define MIN ((A) <= (B)? (A):(B))
解答:两数比较返回其中一个较小值,应该使用三目运算符。注意:需要使用足够多的圆括号来保证以正确的顺序结合。
3.使用#define需要注意下面几点:
1)宏名中不能出现空格,替代字符串可以出现字符串。ANSIC允许在参数列表中出现空格。
2)用小括号括住每个参数,并将整个替代字符串用括号括住。
3)用大写字母定义宏名,以区别普通变量,但也可以使用小写字母来定义。
4)有些编译器限制宏只能定义一行。
5)宏的一个优点不用检查它的变量类型。
6)在宏中不要使用增量或减量运算符。
3.预处理器标识#error、#warning的目的是什么?
#error 字符串 => 表示产生一个错误信息。
#waring 字符串 => 表示产生一个警告信息
4.嵌入式系统中经常要用到的无限循环,如何编写死循环?
while(1){...;}
for(;;){...;} for循环中可以让一个或多个表达式为空(但不能遗漏分号),中间的那个控制表达式为空会被认为是真,所以循环会被永远执行下去。
loop;...;goto loop;
5.用a给出下面的定义(重点看看)
1)一个整型数
int a;
2)一个指向整型数的指针
int *a;
3)一个指向指针的指针,它指向的指针是指向一个整型数
int **a;
4)一个有10个整型数的数组
int a[10];
5)一个有10指针的数组,每一个指针指向一个整型数
int *a[10]; 指针数组
6)一个指向10个整型数的数组指针
int (*a)[10]; 数组指针
7)一个指向函数的指针,该函数有一个整型参数并返回一个整型数
int (*a)(int); 函数指针
8)一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数
int (*a[10])(int);
解答:
int *a[10]:指针数组:首先它是一个数组,数组的元素是指针,如:int b[5],它是整型数组,首先它是一个数组,数组的元素是整型数据。
int (*b)[10]:数组指针:首先它是一个指针,它指向一个数组。
int (*c)(int):函数指针,首先它是一个指针,它指向一个函数。
这里需要明白符号之间的有优先级问题,“[ ]”的优先级比“*”高。a与“[ ]”结合,构成数组,a为数组名。int *是修饰数组元素类型的。
“()”比“[ ]”的优先级高,“*”和b结合构成了指针的定义,int是修饰数组元素类型的。数组在这里是没有数组名的,它是个匿名数组,现在我们知道了b是一个指针,指向一个包含10个元素的数组,所以b是一个数组指针。
6.关键字static的作用
static修饰全局变量、局部变量和函数。
解答:
static修饰全局变量时,这个全局变量又叫静态全局变量。该变量具有静态存储时期、文件作用域和内部链接,仅在编译时初始化一个,如果没有明确初始化,默认值为0。
当静态全局变量要出现在其他函数中时,需要用extern进行声名。全局变量和局部变量可以同名,但在局部变量的作用域中,全局变量不起作用。
全局变量可以作用于整个源程序(源程序包括多个源文件),静态全局变量只作用于一个源文件,所以在其他源文件中起同名的变量名不会起冲突。
static修饰局部变量时,这个变量叫静态局部变量。该变量具有静态存储时期、代码作用域和空连接,仅在编译时初始化一次。如果没有明确初始化。默认值为0。
函数调用结束后存储区空间不释放,保留其当前值。
static修饰函数时,这个函数叫静态函数。只可以在定义它的文件中使用。
7.const关键字的作用
const修饰的数据类型是指常类型,常类型的变量或对象的值是不能被改变的。或者说const修饰的对象意味着只读。
(1)在定义const对象时就进行初始化,因为之后就没机会修改了。
(2)对指针来说,可以指定指针本身为const类型,也可以指定指针所指的对象为const类型,或者二者均为const类型。
(3)在一个函数声明中,若指定它的形参为const类型,则表明该参数是一个输入参数,在函数内部不能改变其值。
(4)对于类的成员函数,若指定其为const类型,则表明该函数时一个常函数,不能修改它的成员变量。
作用:保护对象不被意外的修改,增强程序的健壮性。
8.关键字volatile的介绍
volatile关键字是指一种类型修饰符,跟变量类型差不多,但它是作为指令的关键字,作用是确保本条指令不会因编译器的优化而省略,要求每次直接读值。直接读值指内存中直接装载内容,而不是从寄存器拷贝内容。
volatile的使用:
多线程应用中被几个任务共享的变量需要用到volatile.
当两个线程都要改变某一个变量时,该变量就需要用volatile来声名。该关键字的作用是防止优化编译器把变量从内存装入cpu寄存器中。如果变量装入寄存器中,可能会出现一个线程使用寄存器中的变量,以使用内存中的变量,这就会造成程序执行错误。volatile的意思是让编译器每次操作该变量时一定从内存中取出,而不是从寄存器中取出的。
9.嵌入式中总是会对变量或寄存器进行位操作
给定一个变量a,写两端代码,第一个设置a的bit 3,第二个清除a的bit 3。以上操作不会改变其他位。
#define BIT3 (0x1 << 3) //左移n位:原来值的最左边的n位去掉,右边不n个0。
static int a;
void set_bit3(void){
a |= BIT3;
}
viid clear_bit3(void){
a &= ~BIT3;
}
10.嵌入式系统经常要求程序员去访问某个特定的内存位置。在某工程中,要求设置一绝对地址为0x07a9的整型变量的值为0xaa66.编译器是一个纯粹的ANSI编译器。写出代码。
int *ptr;
ptr = (int *)0x67a9;
*ptr = 0xaa66;
解答:强制类型转换
11.终端是嵌入式系统重要的组成部分,这会导致编译开发商提供自己开发分支持标准C的中断,具体代表事实是产生了一个新的关键字_interrupt。下面的代码就使用了_interrupt关键字去定义了一个中断服务子程序(ISR),评价下面代码。
_interrupt double computer_area(double radius){
double area = PI * radius * radius;
printf("\nArea = %f", area);
return area;
}
解答:
1)中断服务子程序不能有返回值。
2)中断服务子程序不能有参数。
3)在许多的处理器/编译器中,浮点数一般都是不可重入的。有些处理器/编译器需要让额处的寄存器入栈,有些处理器/编译器是不允许中断服务子程序做浮点数运算的,另外,ISR应该是短而有效率的,因此在中断服务子程序中做浮点运算时不明智的。
4)printf函数经常有重入和性能上的问题。
12.下面的代码是输出什么?
void foo(void){
unsigned int a = 6;
int b = -20;
(a+b > 6) ? puts("> 6") : puts(“<= 6”);
}
解答:当表达式中存在有符号数类型和无符号数类型时,所有操作数都自动转换为无符号类型。因此。-20变成了一个非常大的正整数,所以该表示计算出来的结果大于6。
除了有符号类型和无符号类型混合使用时自动转换为无符号类型,较小的类型和较大的类型混合使用会被转换成较大的类型,防止数据丢失。
如下代码:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(void){
int a = 10;
printf("sizeof((a > 5) ? 4 : 8.0) = %d\n", sizeof((a > 5) ? 4 : 8.0));
return 0;
}
输出结果是:sizeof((a > 5) ? 4 : 8.0) = 8;(吧变量强制类型转换为8了)。
13.评价下面的代码片段:
unsigned int zero = 0;
unsigned int compzero = 0xffff;
对于一个int 型不是16位的处理器来说,上面的代码是不正确的,应该改成:
unsigned int compzero = ~0。
14.尽管不像非嵌入式计算机那么常见,嵌入式系统是有从堆(heap)中动态分配内存的过程的。那嵌入式动态分配内存可能会发生哪些问题:
1)越界,理论上可以申请4G的内存,但超过内存的实际大小就会动态申请失败,会返回一个空指针,因此在动态申请内存时要检查返回的指针是否为空。
2)用完内存后记得释放以免内存泄漏,另外在使用过程中不要越界,这样会导致致命错误。
3)使用free或者delete释放内存后,没有将指针设为NULL,会导致野指针。
函数用法:
type *p;
p = (type *)malloc(n * sizeof(type));
if (NULL == p){ //这句是必须的。
perror("error...");
exit(1);
}
/*其他代码*/
free(p);
p = NULL;
对上面函数的说明:
1)malloc函数返回的是void *类型,必须通过(type *)进行强制类型转换。
2)malloc函数的实参为sizeof(type),用于指明一个type型数据需要的大小。
3)申请内存后必须检查是否分配成功。
4)当不再需要使用申请的内存时,记得释放,且指释放一次。如果把指针作为参数调用free释放函数,则函数结束后会成为野指针(如果一个指针既没有捆绑过一个地址或者也没有被记录空地址则成为野指针。)所以释放后应该把指向这块内存的指针指向NULL,防止程序后面会不小心使用了它。
5)要求malloc和free符合一夫一妻制,如果申请了不释放就是内存泄漏,如果无故释放那就是什么也没做。释放只能进行一次,如果释放一次以上会出现错误(释放空指针列外,释放空指针等于啥也没做,所以释放空指针多少次都没有问题。)
15.typedef在c语言中频繁用以声名一个已经存在的数据类型的同义字,也可以用预处理器做类似的事。例如下面的列子:
#define dPS struct s *
typedef struct s* tPS;
以上两种情况的意图都是要定义一个指向结构体s的指针dPS和tPS。这两种方法哪一种更好呢?
解答:
type更好。
首先要了解typedef和define的区别,宏定义知识简单的字符串替换,是在预处理完成的,而typedef是在编译时处理的,它不是简单地代码替换,而是对类型说明符的重新命名。被命名的标识符具有类型定义说明的功能。
上面的两种情况,从形式上看二者相似,但实际使用却不相同。
dPS p1,p2;
在宏替换后,struct s* p1, p2;定义了一个指向结构体的指针p1和一个结构体变量p2。
tPS p3, p4;
用typedef替换后,正确的定义了两个结构体指针p3,p4。
总结, typedef和#define的不同之处:
1)与#define不同,typedef给出的符号名称仅限于对类型,而不是对值。简而言之,typedef仅对类型进行替换(重命名)。
2)typedef的处理由编译器执行,而不是处理器来执行。
3)虽然typedef范围有限,但在其受限范围内,typedef比#define更灵活。
16.C语言同意一些令人震惊的结构,下面的结构合法吗?
int a = 5, b = 7, c;
c = a +++ b;
解答:
合法。
#include <stdio.h>
int mian(void){
int a = 5, b = 7, c;
c = a +++ b;
printf("c = %d\n", c);
return 0;
}
输出结果:12
可将c = a +++ b看做:
c = a++ + b;
或者看做:
c = a + ++b;
两者结果是不同的。
上面的代码被编译器处理成:c = a++ + b;故结果为12.