/*
大部分内容来自《高质量C++/C 编程指南》 和《嵌入式程序员应知道的0x10个问题》的补充整理
*/
1 如何避免重复包含头文件?
答:使用#ifndef/#define/endif.
2 #include <filename.h> 和 #include “filename.h” 有什么区别?
答:对于#include <filename.h> ,编译器从标准库路径开始搜索 filename.h
对于#include “filename.h” ,编译器从用户的工作路径开始搜索 filename.h
3 用预处理指令#define 声明一个常数,用以表明1年中有多少秒(忽略闰年问题)
答:#define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL
//注意以下几点:
1) #define 语法的基本知识(例如:不能以分号结束,括号的使用,等等)
2)懂得预处理器将为你计算常数表达式的值,因此,直接写出你是如何计算一年中有多少秒而不是计算出实际的值,是更清晰而没有代价的。
3) 意识到这个表达式将使一个16位机的整型数溢出-因此要用到长整型符号L,告诉编译器这个常数是的长整型数。
4 分别用宏定义和函数实现:free一段内存并将指针置为空.
答:宏定义:#define MY_FREE(ex) free((ex));/
ex = NULL;
函数: void MyFree(void **ex)
{
free(*ex);
*ex = NULL;
}
下面的回答是错误的,因为指针没有被赋值为空:
{
free(ex);
ex = NULL;
}
5 const 有什么用途?
答:(1) 关键字const的作用是为给读你代码的人传达非常有用的信息,实际上,声明一个参数为常量是为了告诉了用户这个参数的应用目的。
(2) 通过给优化器一些附加的信息,使用关键字const也许能产生更紧凑高效的代码。
(3) 合理地使用关键字const可以使编译器很自然地保护那些不希望被改变的参数,防止其被无意的代码修改。简而言之,这样可以减少bug的出现。注意是代码修改,无法防止DMA、其他CPU、其他线程等编译器不能知道的隐式修改。
6 static有什么用途?
答:(1)用来修饰全局变量,该变量只能在模块内被引用,提高封装性和健壮性。如果不得已需要定义全局变量并且该全局变量需要被其他模块访问,建议如下做法:
/****************source code**********************/
static int gExampleVar = 0;
int GetExampleVar(void)
{
return gExampleVar;
}
{
gExampleVar = var;
}
/************************************************/
(2)用来修饰函数,该函数只能在模块内被调用,提高封装性和健壮性。
(3)用来修饰局部变量,变量的值会像全局变量一样被保留。不建议使用。
7 volatile的用途?并给出例子。
答:一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子:
(1) 并行设备的硬件寄存器(如:状态寄存器),或者GPIO等等。
(2) 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
(3) 多线程应用中被几个任务共享的变量
(4)可以被不止一个CPU访问的变量,如ARM和DSP双核均可访问的变量;或者可以被CPU和DMA访问的变量。
总之,当变量对应的地址中存储的内容,除了可能被当前线程显式修改外,还可能被当前cpu的其他线程、其他cpu、硬件设备隐式修改时,必须使用volatile修饰。优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。
附: 一个变量既可以是const还可以是volatile,因为实质上,const和volatile扯不上半点关系,const修饰一个变量是不允许该变量被代码显式修改,而volatile修饰一个变量表示该变量可能被隐式修改。一个例子是只读的状态寄存器。它是volatile因为它可能被隐式修改了,它是const因为代码不应该试图去修改它。
8 inline的用途?
答:被inline修饰的函数会被直接编译进该函数的调用者中,会节省函数调用花掉的时间。那为什么不用宏定义,因为可以提供输入参数和返回值的类型检查。为什么不直接写到被调用者函数中,因为可以提高可读性,重用性(意义同宏定义)。
9 用变量a给出下面的定义
a) 一个整型数(An integer)
b)一个指向整型数的指针( A pointer to an integer)
c)一个指向指针的的指针,它指向的指针是指向一个整型数( A pointer to a pointer to an intege)r
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 )
答:a) 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
10 解释下面的定义
const int a;
int const a;
const int *a;
int * const a;
int const * a const;
答:前两个的作用是一样,a是一个常整型数。第三个意味着a是一个指向常整型数的指针(也就是,整型数是不可修改的,但指针可以)。第四个意思a是一个指向 整型数的常指针(也就是说,指针指向的整型数是可以修改的,但指针是不可修改的)。最后一个意味着a是一个指向常整型数的常指针(也就是说,指针指向的整 型数是不可修改的,同时指针也是不可修改的)。
11 定义一个函数指针变量和声明一个函数指针类型,并用该指针类型定义一个变量(该函数指针返回类型为int,并带一个int类型的参数)
12 在位宽为32bits的机器上,计算下列sizeof的值?
void Func ( char str[100])
{
sizeof( str ) = //4,指针长度
}
char str[] = “Hello” ;
char *p = str ;
int n = 10;
sizeof (str ) = //6,数组长度,包括’/0’
sizeof ( p ) = //4,指针长度
sizeof ( n ) = //4,int 型指针长度
void *p = malloc( 100 );
sizeof ( p ) = //4,指针长度
答:4,6,4,4,4
13 内存分配
14 给定一个整型变量a,写两段代码,第一个设置a的bit 3,第二个清除a 的bit 3。
答:
#define BIT3 (0x1 << 3)
static int a;
void set_bit3(void)
{
a |= BIT3;
}
void clear_bit3(void)
{
a &= ~BIT3;
}
15 评价下面的代码片断:
unsigned int zero = 0;
unsigned int compzero = 0xFFFF;
/*1's complement of zero */
答:对于一个int型不是16位的处理器为说,上面的代码是不正确的。应编写如下:
unsigned int compzero = ~0;
16 下面的代码就使用了__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()经常有重入和性能上的问题。
17 解释ZI,RW,RO,加载域,运行域。
答:RO就是readonly,包括代码和const 数据;RW就是read/write,包含被初始化成非0值的全局变量;ZI就是zero,包含没有初始化和被初始化成0的全局变量。
加载域指binary烧入Flash中的状态,该 binary包含RW和RO段,对于比较简单的情况,可以在ADS集成开发环境的ARM LINKER选项中指定RO BASE和 RW BASE,告知连接器RO和RW的连接基地址。对于复杂情况,如RO段被分成几部分并映射到存储空间的多个地方时,需要创建一个称为“分布装载描述文件”的文本文件,通知连接器把程序的某一部分连接在存储器的某个地址空间。需要指出的是,分布装载描述文件中的定义要按照系统重定向后的存储器分布情况进行。
Binary之所以不包含ZI数据,是因为ZI数据都是0,没必要包含,只要程序运行之前将ZI数据所在的区域一律清零即可。包含进去反而浪费存储空间。
运行域指程序运行时存在于RAM中的状态,此时会给ZI段分配内存空间。