第一份
参考链接: 面试积累.
1. 用预处理指令#define 声明一个常数,用以表明1年中有多少秒(忽略闰年问题)
#define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL
注意点:
1). #define 标识符 字符串 --基本知识
凡是以“#”开头的均为预处理命令。“define”为宏定义命令。“标识符”为所定义的宏名。“字符串”可以是常数、表达式、格式串等
宏定义不是说明或语句,在行末不必加分号,如加上分号则连分号也一起置换。
宏定义必须写在函数之外,其作用域为宏定义命令起到源程序结束。如要终止其作用域可使用#undef命令
注意括号的使用
2). 懂得预处理器将为你计算常数表达式的值,可直接写出你是如何计算一年中有多少秒而不是计算出实际的值
3). 意识到这个表达式将使一个16位机的整型数溢出,因此要用到长整型符号L,最好为UL
4). 提一手防止重复定义(防止头文件重复的定义申明):#ifndef…#define…#else…#endif
5).注意宏定义中#error的使用,用#error 可以用于检查一个宏是否已经被定义。#error表示预处理器遇到这个命令时,将停止编译,并打印用户自定义的错误信息
用宏定义写出 swap(x,y),即交换两数,不使用第三变量的情况下
#define swap(x,y) ( (x)=(x)+(y); (y)=(x)-(y); (x)=(x)-(y); )
//任何变量一定要打小括号,结尾不要加;
带参宏和带参函数的区别
带参宏:在编译程序时处理;无需定义形参;不会占用存储空间;不会占用运行时间
带参函数:在运行程序时处理;需要定义形参;会占用存储空间;调用和返回时会占用运行时间
程序的编译过程:预处理、编译、汇编、链接。
预处理:预处理相当于根据预处理命令组装成新的C程序,不过常以i为扩展名。
编译: 将得到的i文件翻译成汇编代码,文件后缀为.s文件。
汇编: 将汇编文件翻译成机器指令,并打包成可重定位目标程序的O文件。该文件是二进制文件,字节编码是机器指令。
链接: 将引用的其他O文件并入到我们程序所在的o文件中,处理得到最终的可执行文件。
#ifdef XX
...
#error "XX has been defined"
...
#else
...
#endif
如果 XX 被定义了的话,编译的时候编译器就会报错,error c320 "XX has been defined".
2.用变量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
3.关键字static的作用是什么?
1). 在函数体内,声明静态局部变量,存储于全局数据区,其值保存不释放
普通局部变量是再熟悉不过的变量了,在任何一个函数内部定义的变量(不加static修饰符)都属于这个范畴。编译器一般不对普通局部变量进行初始化,也就是说它的值在初始时是不确定的,除非对其显式赋值。
- 普通局部变量存储于进程栈空间,使用完毕会立即释放。
静态局部变量使用static修饰符定义,即使在声明时未赋初值,编译器也会把它初始化为0。且静态局部变量存储于进程的全局数据区,即使函数返回,它的值也会保持不变。
- 静态局部变量在全局数据区分配内存空间;编译器自动对其初始化其作用域为局部作用域,当定义它的函数结束时,其作用域随之结束
void fn(void)
{
int n = 10; //普通局部变量
printf("n=%d\n", n);
n++;
printf("n++=%d\n", n);
}
void fn_static(void)
{
static int n = 10; //静态局部变量
printf("static n=%d\n", n);
n++;
printf("n++=%d\n", n);
}
int main(void)
{
fn();
printf("--------------------\n");
fn_static();
printf("--------------------\n");
fn();
printf("--------------------\n");
fn_static();
return 0;
}
n=10 //结果函数结束释放变量
n++=11
static n=10 //结果函数结束变量保存
n++=11
n=10
n++=11
static n=11
n++=12
2).在函数体外(但在模块内),声明静态全局变量,存储于全局数据区,其值保存不释放
普通全局变量对整个工程可见,其他文件可以使用extern外部声明后直接使用。也就是说其他文件不能再定义一个与其相同名字的变量了(否则编译器会认为它们是同一个变量)。
静态全局变量仅对当前文件可见,可以被模块内所用函数访问,但不能被模块外其它函数访问。它是一个本地的全局变量,其他文件不可访问,其他文件可以定义与其同名的变量,两者互不影响。
在定义不需要与其他文件共享的全局变量时,加上static关键字能够有效地降低程序模块之间的耦合,避免不同文件同名变量的冲突,且不会误使用。
static函数的使用方式与静态全局变量类似,在函数的返回类型前加上static,就是静态函数。其特性如下:
静态函数只能在声明它的文件中可见,其他文件不能引用该函数 ;不同的文件可以使用相同名字的静态函数,互不影响
非静态函数可以在另一个文件中直接引用,甚至不必使用extern声明
4.关键字const是什么含意?
const----只读常变量。用 const 定义的变量的值是不允许改变的,即不允许给它重新赋值,即使是赋相同的值也不可以。因此如果要给变量赋值,只能必须在定义的时候就给它赋初值。
用 const 修饰的变量,无论是全局变量还是局部变量,生存周期都是程序运行的整个过程。
经过 const 修饰过的变量存储在内存中的“只读数据段”中。只读数据段中存放着常量和只读变量等不可修改的量。
有时候, const 定义的变量仍然不能作为数组的长度
const int a;
int const a;
const int *a;
int * const a;
int const * const a;
第一个和第二个的作用是一样,定义a是一个常整型数。
第三个意味着a是一个指向常整型数的指针(也就是,整型数是不可修改的,但指针可以)。
第四个意思a是一个指向整型数的常指针(也就是说,指针指向的整型数是可以修改的,但指针是不可修改的)。
第五一个意味着a是一个指向常整型数的常指针(也就是说,指针指向的整型数是不可修改的,同时指针也是不可修改的)。
合理地使用关键字const可以使编译器很自然地保护那些不希望被改变的参数,防止其被无意的代码修改。
5. 关键字volatile有什么含意 并给出三个不同的例子。
计算机在执行程序时,每条指令都是在CPU中执行的,而执行指令过程中会涉及到数据的读取和写入。由于程序运行过程中的临时数据是存放在主存(物理内存)当中的,这时就存在一个问题,由于CPU执行速度很快,而从内存读取数据和向内存写入数据的过程跟CPU执行指令的速度比起来要慢的多,因此如果任何时候对数据的操作都要通过和内存的交互来进行,会大大降低指令执行的速度。
为了处理这个问题,在CPU里面就有了高速缓存(Cache)的概念。当程序在运行过程中,会将运算需要的数据从主存复制一份到CPU的高速缓存当中,那么CPU进行计算时就可以(优化器)直接从它的高速缓存读取数据和向其中写入数据,当运算结束之后,再将高速缓存中的数据刷新到主存当中。
然后,可能出现的问题是,当两个线程分别读取了t的值,假设此时t的值为0,并且把t 的值存到了各自的高速缓存中,然后线程1对t进行了加1操作,此时t的值为1,并且把t的值写回到主存中。但是线程2中高速缓存的值还是0,进行加1操作之后,t的值还是为1,然后再把t的值写回主存
volatile提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,都会直接从变量地址中读取数据,而不是使用保存在寄存器里的备份。
volatile 声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问
一般说来,volatile用在如下的几个地方:
- 中断服务程序中修改的供其它程序检测的变量需要加volatile(会访问到的非自动变量);
- 多任务环境下各任务间共享的标志应该加volatile;
- 存储器映射的硬件寄存器(状态寄存器)通常也要加volatile说明,因为每次对它的读写都可能由不同意义;
6.给定一个整型变量a,写两段代码,第一个设置a的bit 3,第二个清除a 的bit 3。
用 #defines 和 bit masks 操作。这是一个有极高可移植性的方法,是应该被用到的方法。最佳的解决方案如下:
#define BIT3 (0x1<<3)
static int a; // 注意|=和&=~操作。
void set_bit3(void)
{
a |= BIT3;
}
void clear_bit3(void)
{
a &=