1.define宏定义和const常变量区别?
a. define 是在预处理阶段,将define定义的内容进行替换。so,程序运行时,常量表中并没有用define定义的常量,系统不为它分配内存。
const定义的常量,在程序运行时在常量表中,系统为它分配内存。
b. define定义的常量,预处理时只是直接进行了替换。所以编译时不能进行数据类型检验。
const定义的常量,在编译时进行严格的类型检验,可以避免出错。
c. define定义表达式时要注意“边缘效应”,例如如下定义:
#define N 2+3 //我们预想的N值是5,我们这样使用N
int a = N/2; //我们预想的a的值是2.5,可实际上a的值是3.5
原因在于在预处理阶段,编译器将 a = N/2处理成了 a = 2+3/2;这就是宏定义的字符串替换的“边缘效应”因此要如下定义:
#define N (2+3)
const定义表达式没有上述问题。
const定义的常量叫做常变量原因有二:
const定义常量像变量一样检查类型
const可以在任何地方定义常量,编译器对它的处理过程与变量相似,只是分配内存的地方不同
------------------------------------------------------------------------------------------------------
1.const定义的只读变量在程序运行过程中只有一份拷贝(因为它是全局的只读变量,存放在静态区),
而#define定义的宏常量在内存中有若干个拷贝。
2.#define宏是在预编译阶段进行替换,而const修饰的只读变量是在编译的时候确定其值。
3.#define宏没有类型,而const修饰的只读变量具有特定的类型
===========================================
const int *p; //p可变,p指向的对象不可变
int const*p; //p可变,p指向的对象不可变
int *const p; //p不可变,p指向的对象可变
const int *const p; //指针p和p指向的对象都不可变
这里给出一个记忆和理解的方法:
先忽略类型名(编译器解析的时候也是忽略类型名),我们看const离哪个近。"近水楼台先得月",离谁近就修饰谁。
判断时忽略括号中的类型
const (int) *p; //const修饰*p,*p是指针指向的对象,不可变
(int) const *p; //const修饰*p,*p是指针指向的对象,不可变
(int)*const p; //const修饰p,p不可变,p指向的对象可变
const (int) *const p; //前一个const修饰*p,后一个const修饰p,指针p和p指向的对象都不可变
2.写一个“标准”宏MIN,这个宏输入两个参数并返回较小的一个。
#define MIN(A,B) ( (A) <= (B)? (A) : (B) )
#define Min(a,b) ((a)<(b)?(a):(b))
#define min(x, y) ({
typeof(x) _min1 = (x); \
typeof(y) _min2 = (y); \
(void) (&_min1 == &_min2); \
_min1 < _min2 ? _min1 : _min2;
})
从一个简单的宏定义看linux内核的严谨
最近闲来无事,分析下linux kernel里面一些函数都是怎样定义使用的,它们都是怎样避免风险的,
从include/linux/kernel.h中挑出一个经典的min宏进行分析一下
(例子一)
从我们最先认识的min函数开始看看,这个函数的作用就是求两个数中小一点的那个
#define min(x, y) ({ \
typeof(x) _min1 = (x); \
typeof(y) _min2 = (y); \
(void) (&_min1 == &_min2); \
_min1 < _min2 ? _min1 : _min2;
})
1)首先我们可以看到这个宏的定义外面由一个({}),为什么要这样写呢?其实{}很好理解,因为我们的宏是多个语句,假如没有了这个{},在一些语句中就会出现歧义,比如 if(a)min(x,y),这样就会出问题,后面的语句它就不会执行了。那么这里为什么在外面还要加一个()呢,我们来看这个函数的作用,它是用来返回一个较小的数,对,是返回,一般来说对于返回值,我们怎么用?最简单的当然是return了,所以,一般来讲,我们会写return min(x,y);(当然还有很多常用的方法,比如说赋值a=min(x,y)等等)。这个时候你就不难理解我们为什么要加个()了吧,不过正规的来讲,这是GCC扩展中的一个用法,在GCC扩展中允许吧一个()内的复合语句看成是一个表达式,称为语句表达式,它可以出现在任何语句表达式可以出现的地方。
2)继续看这个宏,我们知道一般我们使用这个函数的时候,x,y是什么类型的并不清楚,我们是否需要所有的类型都写一个min函数呢?其实不需要,在GCC的扩展中,我们提供了一个宏,称为typeof,它可以获得相应参数的类型,这样就可以不需要因为类型而重写宏了,小方便啊。
再看这两句话:
typeof(x) _min1 = (x); \
typeof(y) _min2 = (y); \
为什么要把x,y赋值给_min1和_min2然后返回_min1和_min2,而不直接写成(x) < (y)? (x) :(y)呢?这个就是最经典的C语言课本中所说的那个所谓的++的问题了,还需要多说吗?好吧,我说一下吧,因为我也是菜鸟,也记不清当时是在什么地方提到这个东西了,呵呵,不过印象中记得好像是这样说的:假如我写成min(a++,b++),展开成宏就变成了(a++) < (b++)? (a++) :(b++)了,究竟a和b各自++了多少次,大概可以算出来吧,所以我们一定要先把它们赋值给两个局部变量,这样就没有这个问题了。
3)下面再来看一下那个(void) (&_min1 == &_min2);这句话是为了在比较不同类型的时候能够打印一个警告信息。好吧,我试试看,写一个简单测试程序
#include <stdio.h>
#define min(x, y) ({ \
typeof(x) _min1 = (x); \
typeof(y) _min2 = (y); \
(void) (&_min1 == &_min2); \
_min1 < _min2 ? _min1 : _min2; })
main(){
unsigned long a = 4;
in b = 5;
printf("min is %d\n", min(a, b));
}
用gcc编译了一下,果然报下面警告了:
min.c: In function ‘main’:
min.c:14: 警告:比较不相关的指针时缺少类型转换
没有这句话,就不会报这个警告,为什么会这样呢?原来,在比较地址的时候,会发现地址所存值的类型,假如不同的话就会报警告,而在直接比较值的时候,比如上面的_min1 < _min2 ?的时候,会做强制类型转换,而且不会报警告,好吧,我真的很佩服写这段代码的人,太牛了!简单的4,5行代码,竟然有这么多的东西在里面,这叫老夫如何是好啊,继续努力。
(例子二)
在Linux内核中,经常会看到do{}while(0)这样的语句,许多人开始都会疑惑,认为do{}while(0)毫无意义,因为他只会执行一次,加不加do{}while(0)效果是完全一样的,其实do{}while(0)主要用于宏定义中。
这里用一个简单点的宏来演示:
# define SAFE_FREE( p) do { free ( p) ; p = NULL ; } while ( 0)
假设这里去掉do...while(0),即定义SAFE_FREE为:
# define SAFE_FREE( p) free ( p) ; p = NULL ;
那么以下代码:
if ( NULL ! = p)
SAFE_FREE( p)
else
. . .
会被展开为:
if ( NULL ! = p)
free ( p) ; p = NULL ;
else
. . .
展开的代码中存在两个问题:
if 分支后面有两个语句,导致else分支没有对应的if,编译失败;
假设没有else分支,则SAFE_FREE中的第二个语句无论if测试是否通过都会执行。
将SAFE_FREE的定义加上{}就可以解决上诉问题了,即:
# define SAFE_FREE( p) { free ( p) ; p = NULL ; }
这样,代码:
if ( NULL ! = p)
SAFE_FREE( p)
else
. . .
会被展开为:
if ( NULL ! = p)
{ free ( p) ; p = NULL ; }
else
. . .
但是,在C程序中,每个语句后面加分号是一种约定俗成的习惯,那么,如下代码:
if ( NULL ! = p)
SAFE_FREE( p);
else
. . .
将为扩展为:
if ( NULL ! = p)
{ free ( p) ; p = NULL ; } ;
else
. . .
这样,else分支就有没有对应的if了,编译将无法通过。假设用了do{}while(0),情况就不一样那个了,同样的代码会被扩展为:
if ( NULL ! = p)
do { free ( p) ; p = NULL ; } while ( 0) ;
else
. . .
不会再出现编译问题。do{}while(0)的使用完全是为了保证宏定义的使用者能无编译出错的使用宏,它不对其使用者做任何假设.
3.用预处理指令#define 声明一个常数,用以表明1年中有多少秒(忽略闰年问题)
#define SECONDS_PER_YEAR (365*24*60*60)UL
#define SECONDS_PER_YEAR (60UL * 60UL * 24UL * 365UL)
或者
#define SECONDS_PER_YEAR ((unsigned long)(60 * 60 * 24 * 365))
4.C中预处理命令 #error 作用?
#error 预处理指令的作用是,编译程序时,只要遇到#error 就会生成一个编译错误提示消息,并停止编译。其语法格式为:
#error error-message
注意,宏串error-message 不用双引号包围。遇到#error 指令时,错误信息被显示,可能同时还显示编译程序作者预先定义的其他内容。关于系统所支持的error-message 信息,请查找相关资料,这里不浪费篇幅来做讨论。
5.嵌入式系统中经常要用到无限循环,你怎么样用C编写死循环呢?
这个问题用几个解决方案。我首选的方案是:
while(1)
{
}
一些程序员更喜欢如下方案:
for(;;)
{
} //即不设初值,不判断条件,循环变量不增值。无终止地执行循环体。
第三个方案是用 goto
Loop:
...
goto Loop;
6. 用变量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 )
答案是:
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
7. 关键字static的作用是什么?
这个简单的问题很少有人能回答完全。在C语言中,关键字static有三个明显的作用:
1). 在函数体,一个被声明为静态的变量在这一函数被调用过程中维持其值不变。
2). 在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外其它函数访问。它是一个本地的全局变量。
3). 在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是,这个函数被限制在声明它的模块的本地范围内使用。
8.关键字const是什么含意?
const int a;
int const a;
const int *a;
int * const a;
int const * a const;
前两个的作用是一样,a是一个常整型数。第三个意味着a是一个指向常整型数的指针(也就是,整型数是不可修改的,但指针可以)。第四个意思a是一个指向整型数的常指针(也就是说,指针指向的整型数是可以修改的,但指针是不可修改的)。最后一个意味着a是一个指向常整型数的常指针(也就是说,指针指向的整型数是不可修改的,同时指针也是不可修改的)。如果应试者能正确回答这些问题,那么他就给我留下了一个好印象。
9. 关键字volatile有什么含意 并给出三个不同的例子。
一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子:
1). 并行设备的硬件寄存器(如:状态寄存器)
2). 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
3). 多线程应用中被几个任务共享的变量
1). 一个参数既可以是const还可以是volatile吗?解释为什么。
2). 一个指针可以是volatile 吗?解释为什么。
3). 下面的函数有什么错误:
int square(volatile int *ptr)
{
return *ptr * *ptr;
}
10. 嵌入式系统总是要用户对变量或寄存器进行位操作。给定一个整型变量a,写两段代码,第一个设置a的bit 3,第二个清除a 的bit 3。在以上两个操作中,要保持其它位不变。
#define BIT3 (0x1<<3)
static int a;
void set_bit3(void)
{
<span style="white-space:pre"> </span>a |= BIT3;
}
void clear_bit3(void)
{
<span style="white-space:pre"> </span>a &= ~BIT3;
}