[Daozy][C 语言入门课程]第24课 常见面试题分析
当前课程对应视频:待实现
题目1:内存对齐
实例代码
#include <stdio.h>
#pragma pack(4)
struct st1 {
char a;
int b;
short c;
};
struct st2 {
short c;
char a;
int b;
};
int main() {
printf("sizeof(st1) = %d\n", sizeof(st1));
printf("sizeof(st2) = %d\n", sizeof(st2));
return 0 ;
}
运行结果
sizeof(st1) = 12
sizeof(st2) = 8
解题答案
#pragma pack(n)
表示设置为n字节对齐。指定的数值,以字节为单位;缺省数值是8,合法的数值分别是1、2、4、8、16。
对齐规则,分两个层面对齐
- 数据成员各自对齐:
对于结构的各个成员,第一个成员位于偏移为0的位置,以后每个数据成员的偏移量必须是#pragma pack()指定的数和这个数据成员的自身长度中取较小的值的倍数。
成员变量:type var;
较小值 = MIN(#pragma pack(), sizeof(var))
成员偏移量地址 = 较小值的倍数;
- 结构(或联合)本身也要进行对齐
在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行对齐。
内存对齐的主要作用:
- 平台原因(移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。 - 性能原因:
经过内存对齐后,CPU的内存访问速度大大提升。CPU把内存当成是一块一块的,块的大小可以是2,4,8,16字节大小,因此CPU在读取内存时是一块一块进行读取的。假设CPU要读取一个int型4字节大小的数据到寄存器中,分两种情况讨论:
再次假设内存读取粒度为4
- 数据从0字节开始:当该数据是从0字节开始时,很CPU只需读取内存一次即可把这4字节的数据完全读取到寄存器中。
- 数据从1字节开始:当该数据是从1字节开始时,问题变的有些复杂,此时该int型数据不是位于内存读取边界上,这就是一类内存未对齐的数据。此时CPU先访问一次内存,读取0—3字节的数据进寄存器,并再次读取4—5字节的数据进寄存器,接着把0字节和6,7,8字节的数据剔除,最后合并1,2,3,4字节的数据进寄存器。对一个内存未对齐的数据进行了这么多额外的操作,大大降低了CPU性能。
题目2 static的作用
- static修饰的静态局部变量声明只执行一次,后续重入不再执行声明变量的语句,而且变量的生命周期延长到程序运行结束。Static修饰的局部变量存放在全局数据区的静态变量区。初始化的时候自动初始化为0。
- 程序实例:
#include <stdio.h>
void stat_enter_times() {
// 该语句只执行一次
static int count = 0;
count++;
printf("count = %u \n", count);
}
int main() {
int i = 0;
for (; i < 10; i++) {
stat_enter_times();
}
}
- 运行结果:
count = 1
count = 2
count = 3
count = 4
count = 5
count = 6
count = 7
count = 8
count = 9
count = 10
- static修饰全局变量的时候,这个全局变量只能在本文件中访问,不能在其它文件中访问,即便是extern外部声明也不可以。
- 程序实例:
文件test.c:
// static 修饰的变量,外部不可访问
static int count;
void stat_enter_times() {
count++;
}
文件main.c:
#include <stdio.h>
// 这一句是要引用test.c内的变量,报语法错误。
// 如果在test.c中去掉static修饰,就不会报错。
extern int count;
int main() {
stat_enter_times();
// count 无法被访问,报语法错误
printf("count = %u \n", count);
}
- static修饰一个函数,则这个函数的只能在本文件中调用,不能被其他文件调用。
- 程序实例:
文件test.c:
static int count;
// static 修饰的函数,外部不可访问
static void stat_enter_times() {
count++;
}
文件main.c:
#include <stdio.h>
// 这一句是要引用test.c内的函数,报语法错误。
// 如果在test.c中去掉static修饰,就不会报错。
extern void stat_enter_times();
int main() {
// 调用报语法错误
stat_enter_times();
}
题目3 extern "C" 的作用
- extern "C"的主要作用就是为了能够正确实现C++代码调用其他C语言代码。加上extern "C"后,会指示编译器这部分代码按C语言的进行编译,而不是C++的。由于C++支持函数重载,因此编译器编译函数的过程中会将函数的参数类型也加到编译后的代码中,而不仅仅是函数名;而C语言并不支持函数重载,因此编译C语言代码的函数时不会带上函数的参数类型,一般之包括函数名。
- 这个功能十分有用处,因为在C++出现以前,很多代码都是C语言写的,而且很底层的库也是C语言写的,为了更好的支持原来的C代码和已经写好的C语言库,需要在C++中尽可能的支持C,而extern "C"就是其中的一个策略。
- 代码实例,常见使用方式:
// 防止该头文件被重复引用
#ifndef _TEST_A_H_
#define _TEST_A_H_
// 告诉编译器,这部分代码按C语言的格式进行编译,而不是C++的
#ifdef __cplusplus
extern "C"{
#endif
/*函数声明*/
#ifdef __cplusplus
}
#endif
#endif
题目4 const的作用
- 实例代码:
// 形式1:a 不允许修改
const int a = 0;
// 试图修改a
a = 1;
// 报错:“错误:向只读变量‘a’赋值”
// 形式2:a 不允许修改
int const a = 0;
// 试图修改a
a = 1;
// 报错:“错误:向只读变量‘a’赋值”
// 形式3:编译报错,不允许这样声明变量
int a const = 0;
// 形式4:指针指向的值不允许修改
int a = 0;
int b = 0;
const int *p = &a;
// int const *p = &a; 等同于上面
// 试图修改*p
*p = b;
// 编译报错:“错误:向只读位置‘*p’赋值”
// 形式5:指针地址不允许修改
int a = 0;
int b = 0;
int * const p = &a;
// 试图修改p
p = &b;
// 编译报错:“错误:向只读变量‘p’赋值”
// 形式6:编译报错,不允许这样声明变量
int a = 0;
int * p const = &a;
// 形式7:指针和值都不能修改
int a = 0;
int b = 0;
const int * const p = &a;
// 试图修改p
p = &b;
// 试图修改*p
*p = b;
编译错误:
test.c:15:2: 错误:向只读变量‘p’赋值
p = &b;
^
test.c:16:2: 错误:向只读位置‘*p’赋值
*p = b;
- 正确写法6种:
// 下面两种写法的效果一样,都是不能修改a的值
const int a = 0;
int const a = 0;
// 下面两种写法的效果一样,都是不能修改*p
const int *p = &a;
int const *p = &a;
// 不能修改 p 的指针地址
int * const p = &a;
// 指针和值都不能修改
const int * const p = &a;
- 编译错误写法
int a const = 0;
int * p const = &a;
网友评论