1 预处理和关键字(22道)
1.1 宏定义是在编译的哪个阶段处理的?
答案:在编译的预处理阶段,被处理的。
编译预处理包括:宏替换、文件包含、条件编译、其他预处理指令。
1.2 写一个“标准”宏定义MIN,这个宏定义有两个参数,返回较小的一个。
#define MIN(A,B) ( (A) <= (B) ? (A) : (B) )
1.3 已知道数组table,用宏求数组元素个数
#define COUNT(table) (sizeof(table) / sizeof(table[0]))
注意:sizeof(table)可以得到数组的长度,sizeof(table[0])获取数组元素的长度
1.4 带参宏和函数的区别
(1) 带参宏只是在编译预处理阶段进行简单的字符替换,而函数则在运行时进行调用和返回。
(2) 宏替换不占用运行时间,只占用编译时间,而函数调用则占用运行时间(分配单元,保留现场,值传递,返回)。
(3) 带参宏在处理的时候不分配内存;而函数调用会分配临时内存。
(4) 宏不存在类型问题,宏名无类型,它的参数也是无类型的;而函数中的实参和形参都要有类型定义,二者的类型要求一致 。
(5) 而使用宏定义次数变多时,宏替换后源程序会变大,而函数调用则不会。
1.5 内联函数的优缺点和适用场景是什么?
(1) 优点:内联函数与宏定义一样会在编译时展开,省去了函数调用的开销,同时又能做类型检查。
(2) 缺点:它会使程序的代码量增大,消耗更多的内存空间。
适用场景:内联函数适用于函数体内代码简单的小函数,对于复杂的函数,即使声明为内联函数,也不一定会被编译器内联。
inline int add(int a.int b)
{
return a + b;
}
1.6 关键字volatile的作用是什么?给出三个不同的例子
(1)作用:告诉编译器不要去优化这个变量的值,因为这个变量可能会被意想不到的改变。
精确的说就是,优化器是在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。
例子1 嵌入式系统中,使用vo1ati1e修饰的寄存器,以确保其值
不能被优化或缓存,例如:
volatile int *led_reg = (int *)0x555555; //指向0x555555地址的寄存器
*led_reg = 0x01;//点亮第一个LED灯
例子2 多线程应用中被几个任务共享的变量,(防止死锁)
在多线程或者异步环境中,使用volatile修饰的变量,以确保其在不同线程或者任务之间同步,例如:在下面的例子中,如果不使用volatile修饰count变量,则可能会出现多个线程中对cout变量值相互独立的情况,使用volatile关键字可以避免这种情况的发生。
volatile int counter = 0;
void increment()
{
while(true)
{
counter++;
}
}
例子3 在并行计算或通信中,使用volat1e修饰的变圣,以确保其能够被共享,
例如:
volatile int input_data[1024];//共享的输入缓冲区
int fetch_input_data()
{
//从设备中读取数据并存储到input_data变量中
...
}
1.7 如何使用C语言实现读写寄存器变量
#define rBANKCON0 ( *(volatile unsigned long *)0x48000004 )
rBANKCON0 = 0x12;
由于是寄存器地址,所以需要先将类型进行强制转换为 volatile unsigned long *,然后再通过*取值,就可以对寄存器进行读写操作了。
1.8 下面代码能不能编译通过?为什么?
#define c 3
c++;
答案:不能,因为宏定义只是简单的字符替换,所以c++会被替换为3++,而3++是错误的。
1.9 "在C语言中,凡是以#开头的都是预处理指令,同时预处理命令都是以#开头的",这句话对不对?
答案:不对,#include <stdio.h>中的#include不是预处理命令,而是预处理指令。
1.10 预处理器标识#error的作用是什么?
答案:#error用于在预处理阶段产生一个错误信息,这个错误信息将被编译器显示出来,然后终止编译过程。
当程序比较大的时候,往往有些定义是在外部指定的(如makefile),或是在系统头文件中指定的,当你不太确定当前是否定义了xxx时,可写如下的预处理代码:
例子1
#ifdef xxx
#error "xxx has been defined"
#else
...
#endif
这样,如果xxx已经被定义,编译器将会报错,否则,就会执行后面的代码。
例子2 查看编译器版本是否符合要求
#if __STDC_VERSION__ < 199901L
#error "C99模式编译器才能编译此程序"
#endif
1.11 用预处理指令#define声明一个常数,用以表明1年中有多少秒(忽略闰年问题)。
#define SECONDS_PER_YER (60 * 60 * 24 * 365)UL
constexpr unsigned long SECONDS_PER_YEAR = (60 * 60 * 24 * 365UL);//对于C++需要将UL放入括号内,否则会编译不过
注意:UL表示无符号长整型,这里用UL是为了防止溢出。
1.12 关键字static的作用是什么?
(1) static修饰局部变量:①改变了其存储位置,储存在静态区;②同时改变了其生命周期,为整个源程序
因此它只被初始化一次,若没显示初始化则自动化初始化为0。
(2) 修饰全局变量:改变了其作用域,只可以被为文件所用的函数访问。
(3) static修饰函数:改变了其作用域,只可以在这一文件内的其他函数调用
1.13 下面是关键字const的使用示例,请说明它们的作用:
const int a = 10; //a是一个整型常量
int const a;//a是一个整型常量
const int *a;//a是一个指向整型常量的指针变量
int *const a;//a是一个指向整型变量的指针常量
int const * const a = &b;//a是一个指向整型常量的指针常量
char *strcpy(char *strDest,const char *strSrc);//参数在函数内部不会被修改
const int strcmp(char *source,char *dest);//函数返回值不会被修改
const放在*的左边是修饰指向的对象,放在*的右边是修饰指针本身。
1.14 一个参数既可以是const还可以是volatile吗?解释为什么,一个指针可以是volatile吗?解释为什么?,下面的函数有什么问题?
一个参数既可以是const还可以是volatile吗?解释为什么?(性质不互相影响)
答案:可以,因为const和volatile修饰的是两个不同的概念,一个是只读,一个是易变,两者之间没有冲突。
一个指针可以是volatile吗?(性质相同)
答案:可以,指针是一个变量,volatile修饰的是变量,所以指针也可以是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;
}
1.15 关键字typedef在C语言中频繁用以声明一个已经存在的数据类型的同义字。也可以用预处理做类似的事情,例如思考下面的例子:
#define dPS struct s *
typedef struct s * tPS p;//(顺序、分号、#号)
以上两种情况的意图都是定义dPS和tPS作为一个指向结构体s的指针,哪种方法更好?为什么?
typedef更好,因为这种方式更直接,更易读,更具有可维护性,#define的方式更容易出现副作用,看例子1,在下面代码中,使用了#define和typedef定义了两个指向结构体s的指针类型p1和p2,其中p2没有使用#define,如果使用#define宏定义的方式,预处理会将所有的dPS替换为struct s *,这样就会导致p1和p2的大小不一致。而如果采用typedef的方式直接定义指针类型,则不会出现这个问题。
因此,为了避免使用#define宏定义和typedef组合定义时可能导致的问题,建议直接使用typedef定义指针类型。
例子1
#define dPS struct s *
typedef struct s * tPS p;
typedef dPS p2;
int main()
{
p1 myp1;
p2 myp2;
printf("sizeof(p1) = %d\n,sizeof(p2) = %d\n",(int)sizeof(p1),(int)sizeof(p2));
return 0;
}
例子2
dPS p1,p2;
tPS p3,p4;
第一行代码扩展为struct s *p1,p2;即定义p1为一个指向结构体的指针,p2为一个实际的结构体,这也许是你不想要的。第二行代码正确的定义了p3和p4两个指针。
1.16 关键字sizeof的作用是什么?函数strlen()呢?
(1)sizeof的作用是用来计算变量,数据类型所占用的字节数。sizeof(数组名)得到数组
所占用的字节数,sizeof(字符串指针名)得到指针所占字节数。
(2)strlen()函数的作用是计算字符串的长度,不包括字符串结束符'\0'。strlen(字符数组名)得到字符串所占字节数,strlen(字符串指针名)得到字符串所占字节数。
1.17 关键字extern的作用是什么?
答案:用于跨文件引用全局变量,即在本文件中引用一个已经在其他文件中定义的全局变量。
解析:
(1)注意引用时不能初始化,如extern var = 0;是错误的。
(2)另外,函数默认是extern类型的,表明是整个程序(工程)可见的,加不加都一样。
1.18 extern "C"的作用是什么?
答案:
(1) 在C++代码中调用C函数。用法:extern "C" void func();例子如下:
(2) 在C代码中调用C++函数。用法:extern "C" void func();例子如下:
注意:
extern "C"只能用于C++文件中,不能用于C文件中。
例子
extern "C"
{
//C函数库文件声明/函数声明。
}
1.19 关键字auto的作用是什么?
答案:用来定义自动局部变量,自动局部变量在进入声明变量的语句块时被建立,
在退出该语句块时被销毁,仅在语句块内部使用。
解析:
其实普通局部变量就是自动局部变量,只是省略了auto关键字。
1.20 关键字register的作用是什么?其使用的时候需要注意什么?
(1)作用:编译器会将register修饰的变量尽可能的存放在CPU的寄存器中,以加快其读取速度,
一般用于频繁使用的变量。
(2)注意:register变量可能不存放在内存中,所以不能用&取该变量的地址;只有在局部变量和形参
可以作为register变量,寄存器数量有限,不能定义过多的register变量。
1.21 C语言编译过程中,关键字volatile和extern分别在哪一个阶段起作用?
答案:volatile在编译阶段起作用,extern在链接阶段起作用。
解析:
C语言编译过程:预处理->编译->汇编->链接
(volatile) (extern)
1.22 关键字const和#define的区别是什么?
(1)异:const有数据类型,编译器可以做静态类型检查;
#define没有数据类型,可能会导致类型出错。
(2)同:两者都可以定义常数。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)