1、C代码变成可执行文件的过程:
-
预处理:
把源文件翻译成预处理文件
gcc -E code.c 显示预处理的结果
gcc -E code.c -o code.i 生成以.i结尾的预处理文件
-
编译:
把预处理文件翻译成汇编文件
gcc -S code.i 生成以.s结尾的汇编文件
-
汇编:
把汇编文件翻译成二进制的目标文件
gcc -c code.s 生成以.o结尾的目标文件
-
链接:
把若干个目标文件合并成一个可执行文件
gcc code.o a.o b.o c.o … 默认生成一个a.out的可执行文件
2、break和continue的区别
1.当它们用在循环语句的循环体时
break用于立即退出本层循环
continue仅仅结束本次循环(本次循环体内不执行continue语句后的其它语句,但下一次循环还会继续执行)
2.如果有多层循环时,break只会跳出本层循环,不会跳出其他层的循环
-
break可用于switch语句,表示跳出整个switch语句块,而continue则不能单独的用于switch语句。但是continue可以用于循环内部的switch语句。
-
break和continue语句在循环内的switch语句中使用时,是有区别的。在这种情况下的break是指跳出switch语句块(switch语句块之后的代码仍然执行)。而这种情况下的continue是指结束本次循环(不在执行switch后面的代码),进行下一次循环
3、switch()中不允许的数据类型有?
(1)浮点型:浮点型无法精确比较,由于精度问题。
(2)字符串:字符串没有直接的比较操作符可以使用,只能通过strcmp之类的函数进行比较,也不适合。
4、static在C语言中的作用
静态全局变量仅对当前文件可见,其他文件不可访问,其他文件可以定义与其同名的变量,两者互不影响。
在定义不需要与其他文件共享的全局变量时,加上static关键字能够有效地降低程序模块之间的耦合,避免不同文件同名变量的冲突,且不会误使用。
非静态全局变量的作用域是整个源程序, 当一个源程序由多个源文件组成时,非静态的全局变量在各个源文件中都是有效的。 而静态全局变量则限制了其作用域, 即只在定义该变量的源文件内有效, 在同一源程序的其它源文件中不能使用它。由于静态全局变量的作用域局限于一个源文件内,只能为该源文件内的函数公用,因此可以避免在其它源文件中引起错误。
把局部变量改变为静态变量后是改变了它的存储方式即改变了它的生存期。把全局变量改变为静态变量后是改变了它的作用域,限制了它的使用范围。
5、进程映像
text 代码段(只读段): 存储二进制指令、常量,权限是只读,强制修改会产生段错误
data 数据段: 存储初始化过的全局变量(如果初始化为0,会被存储在bss),初始化过的静态局部变量
bss 静态数据段: 未初始化过的全局变量、未初始化过的静态局部变量,程序运行时会被清理为0
heap 堆: 由程序员手动管理的,足够大
stack 栈: 存储局部变量、块变量,会随着程序运行不断申请、释放 (由操作系统管理,小)
environ 环境变量表: 环境变量
argv 命令行参数: 程序执行时附加的参数
6、什么是指针:
指针是一种特殊的数据类型,使用它可以定义指针变量,指针变量中存储的是整型数据,代表了内存的编号,通过这个编号可以访问到对应的内存。
为什么使用指针:
1、由于函数与函数之间是相互独立的,但是有些时候需要共享变量
传参是单向值传递
全局变量尽量少用,容易命名冲突
使用数组还需要传递长度
函数的命名空间是相互独立的,但是地址空间是同一个,所以指针可以解决这个问题
2、由于函数传参是值传递(内存拷贝),对于字节数较多的变量,值传递的效率比较低,如果传递的是变量的地址,只需要传递4|8个字节。
3、堆内存无法取名,它不像stack、bss、data让变量名和内存建立关系,只能使用指针来记录堆内存的地址编号从而使用该堆内存。
空指针:
值是NULL的指针变量都是空指针,如果对空指针进行解引用产生段错误
NULL也是一种错误标志,如果一个函数返回值是指针类型时,当函数执行出错可以返回NULL表示该函数执行错误。
注意:NULL在绝大多数的系统中都是0,在个别系统是1
if(NULL==p) 等价于 if(!p)
如何避免空指针带来的段错误:
使用来历不明的指针之前先做判断是否是空指针
1、当函数的参数是指针时,别人传给你的可能就是空指针
2、当函数获取返回值时,也有可能获取到空指针
野指针:
指针的指向不确定的内存的指针。
野指针的危害:
1、段错误
2、脏数据
3、一切正常
野指针危害比空指针更严重,因为它无法判断出来,而且可能是隐藏性的错误,短时间内不暴露
如何避免:
1、定义指针时一定要初始化
2、函数不返回局部变量地址
3、指针指向的内存被释放后,指针变量立即置空 = NULL
7、指针的运算:
绝大多数无意义。
指针+n: 指针+指针类型宽度*n
指针 -n: 指针 -指针类型宽度*n
指针-指针:(指针-指针)/ 指针类型宽度 计算出两个指针直接相隔多少个指针元素
8、堆内存和栈内存的区别:
堆:是进程中的一个内存段,由程序员手动管理。
足够大
为什么要使用堆内存:
1、随着程序的复杂,数据会越来越多
2、其他的内存段的申请和释放不受控制,堆内存的申请释放受程序员控制
全局的,变量可以调整大小
缺点:使用麻烦,容易产生内存碎片
栈:局部的,快速访问,不需要自己管理,自动创建释放,有大小限制,不会产生内存碎片
9、什么时候用堆,什么时候栈:
如果我们需要分配一大块内存(例如一个很大的数组或者一个很大的结构体),而且我们需要保持这个变量很长时间(例如全局变量)。我们应该分配堆内存。如果你处理的很小的变量,而且只要再函数使用的时候存活,那么你应该使用栈,它比较方便而且快捷。如果你需要类似与数组或者结构体的变量,而且能够动态改变大小(例如一个数组可以根据需要添加数据或者删除数据),那么你可以用malloc(),realloc())给他们分配堆内存,用free()手动的管理内存。
10、堆内存的越界后果:
1.超过33页产生段错误
2.破坏了malloc的维护信息,再次使用malloc/free会出错
3.脏数据
11、字符串函数
#include <stdio.h>
#include <assert.h>
size_t str_len(const char* str)
{
assert(NULL != str);
const char* tmp = str;
while(*tmp) tmp++;
return tmp - str;
}
char* str_cpy(char* dest, const char* src)
{
assert(NULL != dest && NULL != src);
char* tmp = dest;
while(*tmp++ = *src++);
return dest;
}
char* str_cat(char *dest, const char *src)
{
assert(NULL != dest && NULL != src);
char* tmp = dest;
while(*tmp) tmp++;
while(*tmp++ = *src++);
return dest;
}
int str_cmp(const char *s1, const char *s2)
{
assert(NULL != s1 && NULL != s2);
while(*s1 && *s1 == *s2) s1++,s2++;
return *s1 - *s2;
}
12、宏的优点
提高代码的拓展性(方便批量修改)、提高可读性、提高安全性
13、什么是宏函数
其实就是带参数的宏,不是真正的函数,不检查参数类型,没有传参,没有返回值,只有计算的结果,只是替换
14、如何避免宏函数二义性
1、宏函数整体加小括号,每个参数都加小括号
2、不要提供带自变运算符的变量作为参数
15、普通函数与宏函数有什么区别?
普通函数:是一段具有某项功能的代码段,会变编译成二进制指令存储到代码段内存中,函数名就是首地址,有独立的命名空间、栈内存
宏函数:是一个带参数的宏,并不是真正的函数,而只是代码的替换,仅仅只是使用起来像函数。
函数: | 返回值 | 类型检查 | 安全 | 压栈、出栈 | 速度慢 | 跳转 |
宏函数: | 运算的结果 | 通用 | 危险 | 替换 | 速度快 | 冗余 |
16、#define typedef区别
#define INT int
typedef int INT
是什么?
区别?
#define 简单的文本替换,不在编译中进行
typedef 起别名
typedef int* pINT;
#define pint int*
pINT a,b;
// a和b都是int*
pint a,b;
// a是int* b是int
因为#define只是单纯替换
17、结构体的对齐补齐
内存对齐:
假定从零地址起,每个成员的起始地址编号必须是它本身字节数的整数倍
内存补齐:
结构体的总字节数必须是它最大成员字节数的整数倍,如果不是,则在末尾填充空字节
在Linux系统下计算结构体的对齐、补齐时,如果成员字节数超过4 ,则按照4字节计算
18、结构与联合的区别
- 结构和联合都是由多个不同的数据类型成员组成, 但在任何同一时刻, 联合中只存放了一个被选中的成员(所有成员共用一块地址空间), 而结构的所有成员都存在(不同成员的存放地址不同)。
- 对于联合的不同成员赋值, 将会对其它成员重写, 原来成员的值就不存在了, 而对于结构的不同成员赋值是互不影响的。
19、字节序列
个人计算机系统一般都是小端系统,UNIX服务器和网络设备都是大端,网络字节序也是大端模式数据
小端:高对高 大端:高对低
判断大小端
union Data
{
char ch;
int num;
};
int main(int argc,const char* argv[])
{
union Data d = {};
d.num = 0x01020304;
if(0x4 == d.ch)
printf("小端\n");
else
printf("大端\n");
}
20、什么是枚举类型
枚举可以看作是一种类型受限的int类型,把所有可能出现的值列出来,但是C语言中编译器为了效率不会检查数据的值
main函数执行之前,还会执行什么代码
全局对象的构造函数会在main函数之前执行
定义一个指向函数指针的指针数组,函数有一个整型入参和一个整型返回值
int (*arr)[10] (int num)