目录
- 1.用预处理指令#define声明一个常数,用以表明1年中有多少秒(忽略闰年问题)?
- 2.写一个“标准”宏MIN,这个宏输入两个参数并返回较小的一个?
- 3.嵌入式系统中经常要用到无限循环,如何用C编写死循环?
- 4.关键字const、static、volatile、extern有什么含义?
- 5.用变量a给出如下定义
- 6.typedef和#define
- 7.进程和线程
- 8.寄存器位操作例题
- 9. 访问一绝对地址把一个整型数强制转换为一指针是合法的
- 10. 下面的代码使用_interrupt关键字定义了一个中断服务子程序(ISR),请找错。
- 11. 下面的代码输出是什么?(考察“c语言中的整数自动转换原则”。)
- 12. 位、字节、字、字长
- 13. 评价代码(处理器字长的重要性)
- 14.
总结了一部分,发现全是软件部分的,后续会继续更新,并添加上嵌入式硬件部分的内容。
1.用预处理指令#define声明一个常数,用以表明1年中有多少秒(忽略闰年问题)?
答:
#define SECONDS_PER_YEAR (60*60*24*365)UL
①注意#define语法的格式
②懂得预处理器会计算常数表达式的值,因此直接写出是如何计算的更清晰
③该表达式将使一个16位机的整型数溢出,因此要用到长整型符号L,告诉编译器这个常数是长整型数。
④用到了UL(表示无符号长整型)。
2.写一个“标准”宏MIN,这个宏输入两个参数并返回较小的一个?
答:
#define MIN(A,B) ((A)<=(B)? (A):(B))
①三重条件操作符,这个操作符存在的原因是它使得编译器能产生比if-then-else更优的代码。(对于条件表达式b?x:y,先计算条件b,然后进行判断,如果b的值为true,则运算结果为x的值,否则为y。)
3.嵌入式系统中经常要用到无限循环,如何用C编写死循环?
答:
while(1) {} 或者 for(;;) {}
4.关键字const、static、volatile、extern有什么含义?
答:
(1)const:
只读,合理地使用const可以使编译器保护那些不希望被修改的参数,防止被无意修改。
const int a;
int const a;
const int *a; // 等价于int const *a;
int *const a;
前两个:a是一个常整型数;
第三个:const修饰int,a是一个指向常整型数的指针,即整型数*a是不可以修改的,但指针却可以修改,可以重新指向另外一个内存地址;
第四个:const修饰指针a,a是一个指向整型数的常指针,即指针指向的整型数是可修改的,但指针不可修改。
(2)static:
①在函数体,一个被声明为静态的变量在这一函数被调用过程中维持其值不变;
②在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外其他函数访问。它是一个本地的全局变量;
③在模块内,一个被声明为静态的函数只可被这一模块内的其他函数调用,也就是,这个函数被限制在声明它的模块的本地范围内使用。
【C语言】模块化编程-通俗易懂
附加问题:static全局变量与普通全局变量有什么区别?static函数与普通函数有什么区别?
全局变量(外部变量)的说明之前再冠以static就构成了静态的全局变量。
全局变量本身就是静态存储方式,两者在存储方式上并无不同。区别在于,非静态全局变量的作用域是整个源程序,静态全局变量则只在定义该变量的源文件内有效,在同一源程序的其它源文件中不能使用它。
static函数与普通函数作用域不同,仅在本文件。只在当前源文件中使用的函数应该说明为内部函数(static修饰的函数),内部函数应该在当前源文件中说明和定义。对于可在当前源文件以外使用的函数,应该在一个头文件中说明,要使用这些函数的源文件要包含这个头文件。
(3)volatile:
volatile关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其它线程等。由于访问寄存器的速度要快过RAM,所以编译器一般都会作减少存取外部RAM的优化。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。使用该关键字的例子如下:
int volatile nVint; //当要使用volatile声明的变量的值的时候,系统总是会从它所在的内存读取数据,即使它前边的指令刚刚从该处读取过数据。
volatile int i = 10;
int a = i;
int b = i;
volatile 指出 i是随时可能发生变化的,每次使用它的时候必须从i的地址中读取,因而编译器生成的汇编代码会重新从i的地址读取数据放在b中。而优化做法是,由于编译器发现两次从i读数据的代码之间的代码没有对i进行过操作,它会自动把上次读的数据放在b中。而不是重新从i里面读。这样以来,如果i是一个寄存器变量或者表示一个端口数据就容易出错,所以说volatile可以保证对特殊地址的稳定访问。
下面是几个例子:
①存储器映射的硬件寄存器通常要加volatile说明,因为每次对它的读写都可能由不同意义;
②中断服务程序中修改的供其它程序检测的变量需要加volatile;
③多任务环境下各任务间共享的标志应该加volatile。
附加问题:
①一个参数既可以是const还可以是volatile吗?
可以。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变,它是const因为程序不应该试图去修改它。
②一个指针可以是volatile吗?
可以。
③下面的函数有什么错误?
int square(volatile int *ptr)
{
return *ptr * *ptr;
}
这段代码是个恶作剧。代码的目的是用来返指针ptr指向值的平方,但是由于ptr指向一个volatile型参数,编译器将产生类似下面的代码:
int square(volatile int *ptr)
{
int a,b;
a = *ptr;
b = *ptr;
return a * b;
}
由于*ptr的值可能被意外地改变,因此a和b可能是不同的。结果这段代码返回的可能不是你所期望的平方值。正确如下:
long square(volatile int *ptr)
{
int a;
a = *ptr;
return a * a;
}
解释:volatile告诉编译器不去优化,也就是说ptr指向的地址存的值可以在很短的时间内变化,那么你在计算乘法的过程中改变了这个值,就得不到正确的结果了。
(4)extern
extern外部声明:如果要在a.c中使用b.c的一个变量avg,就需要用extern int avg;在a.h中声明avg,就可以引用avg了。前提是avg是一个全局变量,局部变量不能用extern修饰。
5.用变量a给出如下定义
a) 一个整型数
b) 一个指向整型数的指针
c) 一个指向指针的的指针,它指向的指针是指向一个整型数
d) 一个有10个整型数的数组
e) 一个有10个指针的数组,该指针是指向一个整型数的
f) 一个指向有10个整型数数组的指针
g) 一个指向函数的指针,该函数有一个整型参数并返回一个整型数
h) 一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数
答:
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
6.typedef和#define
通常使用typedef要比#define好,例如:
typedef char* pStr1;
#define pStr2 char*
pStr1 s1, s2;
pStr2 s3, s4;
#define只是做简单的字符串替换,而使用typedef为char*定义了新类型pStr1后,上述代码相当于:
char *s1, *s2;
char* s3, s4;
7.进程和线程
7.1基本概念
进程和线程都是CPU工作时间段的描述,是运行中的程序指令的一种描述。
背景:电脑的运行,实际就是CPU和相关寄存器以及RAM之间的事情。
基础事实:执行一段程序代码,实现一个功能的过程之前,相关的资源必须到位,万事俱备只欠CPU。然后由操作系统的调度算法选出某个任务让CPU来执行。然后就是PC指针指向该任务的代码开始,由CPU开始取指令,然后执行。
这里引入一个概念:除了CPU以外所有的执行环境,主要是寄存器的一些内容,就构成了进程的上下文环境。进程的上下文是进程执行的环境。当这个程序执行完了,或者分配给它的CPU时间片用完了,那它就要被切换出去,等待下一次CPU的临幸。在被切换出去做的主要工作就是保存程序上下文,因为这是下次它被CPU选中的运行环境。
在CPU看来,所有的任务都是一个一个轮流执行的:先加载进程A的上下文,然后执行A,保存进程A的上下文,调入下一个要执行的进程B的进程上下文,然后执行B。。。
重要:进程和线程就是在这样的背景出来的。
①进程就是上下文切换之间的程序执行部分,是运行中的程序的描述,也是对应于该段CPU执行时间的描述。
②在软件编码方面,我们说的进程是稍有不同的,编程语言中创建的进程是一个无限loop,对应的是tcb块。而这个是操作系统进行调度的单位。
③与进程相关的是寻址空间、寄存器组、堆栈空间等,即不同的进程这些东西都不同。
线程是什么呢?
如果我们把进程比喻为一个运行在电脑上的软件,那么一个软件的执行不可能是一条逻辑执行的,必定有多个分支和多个程序段,就好比要实现程序A,实际分成 a,b,c等多个块组合而成。那么这里具体的执行就可能变成:程序A得到CPU >>>CPU加载上下文,开始执行程序A的a小段,然后执行A的b小段,然后再执行A的c小段,最后CPU保存A的上下文。这里a,b,c的执行是共享了A进程的上下文,CPU在执行的时候仅仅切换线程的上下文,而没有进行进程上下文切换的。进程的上下文切换的时间开销是远远大于线程上下文时间的开销。这样就让CPU的有效使用率得到提高。这里的a,b,c就是线程,也就是说线程是共享了进程的上下文环境,的更为细小的CPU时间段。线程主要共享的是进程的地址空间。
7.2 进程通信?
7.3 死锁?
8.寄存器位操作例题
······嵌入式系统总是要对变量或寄存器进行位操作。给定一个整型变量a,写两段代码,第一个设置a的bit3,第二个清除a的bit3。在以上两个操作中,要保持其它位不变。
#define BIT3(0x1<<3)
static int a;
void set_bit3(void)
{
a |= BIT3;
}
void clear_bit3(void)
{
a &= ~BIT3; //其中,“a &= ~BIT3;”表示:a=a&~BIT3,~的优先级高,所以又等于a=a&(~BIT3)。
}
9. 访问一绝对地址把一个整型数强制转换为一指针是合法的
······嵌入式系统经常具有要求程序员去访问某特定内存位置的特点。在某工程中,要求设置一绝对地址为0x67a9的整型变量的值为0xaa66。
考察“访问一绝对地址把一个整型数强制转换为一指针是合法的”。
int *ptr;
ptr = (int *)0x67a9;
*ptr = 0xaa55;
10. 下面的代码使用_interrupt关键字定义了一个中断服务子程序(ISR),请找错。
_interrupt double compute_area (double radius)
{
double area = PI * radius * radius;
printf("\nArea = %f", area);
return area;
}
错误:
①ISR不能返回一个值;ISR不能传递参数;
②在许多处理器/编译器中,浮点一般都是不可重入的。有些处理器/编译器需要让额外的寄存器入栈,有些就是不允许在ISR中做浮点运算。
③printf()经常有重入和性能上的问题。
11. 下面的代码输出是什么?(考察“c语言中的整数自动转换原则”。)
void foo(void) //foo没什么意义,就是起的一个名字
{
unsigned int a = 6;
int b = -20;
(a+b > 6) ? puts(">6") : puts("<=6");
}
解答:
①puts()是一个输出函数。某些情况下,puts()=printf(),例如:
printf("%s\n", name);
puts(name); 两者是等价的,puts()函数显示字符串时,系统会自动在后面加一个换行符。
但是,如果puts()后面的参数是字符指针变量或字符数组,name括号中除了字符指针变量名或字符数组名之外什么都不能写。例如:
printf("输出结果为:%s\n", str);
puts()就不能写成:puts(输出结果为:str);
②该无符号整型问题的答案输出是“>6”。在C语言操作中,当表达式中存在有符号类型和无符号类型时,所有操作数都自动转换为无符号型。因此-20变成了一个非常大的正整数。
附加题:程序的结果是多少?
unsigned int i = 3;
cout << i * -1;
i是unsigned int型,-1是int型,-1必须转换为unsigned int型,即0xffffffff,十进制为4294967295,然后再与i相乘,如果不考虑溢出,结果是12884901885,十六进制为0x2FFFFFFFD,由于unsigned int只能表示32位,因此结果是0xfffffffd,即4294967293。(在32位机上,int是32位,范围-2147483648至2147483647。)
参考:C语言中的整数自动转换原则
12. 位、字节、字、字长
位(Bit):表示一个二进制数码0或1,是计算机存储处理信息的最基本的单位。
字节(Byte):一个字节由8个位组成。。
字(Word):16个位为一个字(即两个字节)。通常称16位为一个字,32位是一个双字,64位为两个双字。
字长:即字的位数。例如一台8位机,1个字就是1个字节,字长为8位;一台16位机,一个字由2个字节构成,字长为16位。
即,对于不同的CPU,字长的长度不一样。8位的CPU一次只能处理1个字节,32位的CPU一次就能处理4个字节。
13. 评价代码(处理器字长的重要性)
unsigned int zero = 0;
unsigned int compzero = 0xFFFF;
对于一个int型不是16位的处理器,上面的代码不正确,应该是:
unsigned int compzero = ~0;
unsigned int compzero = 0xFFFF; 只写了2个字节,16位的才符合。
32位的可以写成:unsigned int compzero = 0xFFFFFFFF;
但unsigned int compzero = ~0; 更安全,不管有多少位,直接取反,把所有的0变成1。
14.
①程序的局部变量存储在(栈)中,全局变量存储在(静态存储区),动态申请数据存储在(堆)中。
②