面试达人手册 - C语言知识点(持续更新)

目录 - C语言面试集锦

    • 1. 编译器
      • 1.1 什么是GNU?
      • 1.2 什么是GCC?
      • 1.3 GCC的编译流程?
      • 1.4 GCC的各个编译步骤的作用?
    • 2. 如何使用GDB调试器?
    • 3. 静态库/动态库的区别、生成和使用?
      • 3.1 库的命令方式?
      • 3.2 静态库和动态库的比较?
      • 3.3 制作方法?
        • 1)静态库的制作
        • 2)动态库的制作
      • 3.4 使用方法?
        • 1)静态库的使用(e.g. libtest.a)
        • 2)动态库的使用(e.g. libtest.so)
      • 3.5 动态库的加载方法?
    • 4. 关键字
      • 4.1 什么是空类型?
        • 1) void
      • 4.2 const
      • 4.3 extern
      • 4.4 volatile
          • 1)变量用volatile声明的含义:
          • 2)遇到volatile关键字声明的变量:
      • 4.5 register
    • 5. 数组、指针、函数(数组指针、指针数组、函数指针...)
    • 6. 作用域
    • 7. 内存空间分布
    • 8. 内存安全
    • 9. 代码规范

1. 编译器

1.1 什么是GNU?

GNU(“GNU is Not Unix”的递归缩写)是一个自由的操作系统,其软件内容完全以GPL方式发布。创始人Stallman宣称GNU的发音应当为’Guh-NOO’(注:Gnu在英文中原意为非洲牛羚,发音与new相同)。

GNU作为操作系统,但其发展尚未完成,GNU的内核(称为Hurd)是主要拖后腿那个东东。在现实中,GNU系统核心多半使用Linux内核、FreeBSD等替代方案。

1.2 什么是GCC?

Richard Stallman编写GCC的最初愿望是希望有一个C语言的编译器,那时的GCC含义仅是GNU C Compiler而已。多年发展走来,GCC除了能支持C语言,还支持Objective-C、C++、Java等。GCC不单指GNU C语言编译器,俨然成为了GNU编译器家族(GNU Compiler Collection)了。

编译器通过文件的扩展名来分辨程序所用的语言。


1.3 GCC的编译流程?

GCC的编译流程分为4步:

预处理
pre-processing
编译
compiling
汇编
assembling
链接
linking

GCC的命令格式
gcc [命令选项] [目标文件] [源文件]

GCC选项 作用
-o 指定GCC的输出目标文件
-E 编译器在预处理结束后,停止编译流程
-S 编译器在编译完成后,停止编译流程
-c 编译器在完成编译和汇编后,停止编译流程
-Wall 打开所有类型语法警告
-On 使用级别n来优化代码(GCC不同版本的优化效果可能不同)
-O : 主要进行线程跳转和延迟退栈 进行优化
-O2 : 继承O1的优化效果; 还有处理器相关的(e.g. 指令调度)优化等
-O3: 继承O2的优化效果; 还有循环展开等优化;
建议: 在程序调试完成后,再打开优化选项,避免优化问题和bug信息同时处理起来比较困难
-g 添加代码的gdb调试功能

1.4 GCC的各个编译步骤的作用?

GCC支持的编译处理方式表

文件后缀 含义 GCC编译流程 GCC编译命令
.c c语言源程序
.m Objective-C语言源程序
.C/.cc/.cxx/.cpp c++语言源程序
.i 预处理过的C程序 预处理编译、汇编、链接 gcc -E -o test.i test.c

预处理:
1) 去注释
2) 处理“#”开头的预处理部分(如:条件编译宏替换、头文件包含)
3) 预处理变量的处理(如:__file__, __FILE__, __LINE__, __STDC__…)
.ii 预处理过的C++程序 预处理编译、汇编、链接
.s/.S (已经编译过的)汇编程序 预处理、编译汇编、链接 gcc -S -o test.s test.c/test.i

编译:
1) 检查程序规范性和语法错误等
2) 翻译生成汇编文件".s/.S"
.o 已经汇编过的二进制文件 预处理、编译、汇编链接 gcc -c -o test.o test.s

汇编:
1) 生成二进制文件"*.o"
.a/.so 编译后的库文件 预处理、编译、汇编、链接 gcc -o test test.o

链接:
1) 指定函数库路径
2) 多文件整合

2. 如何使用GDB调试器?

GDB是GNU开源组织发布的一个Linux下强大的程序调试工具。

使用步骤 相关命令
1) 带参‘-g’编译源程序test.c,生成a.out gcc -g -o a.out test.c
2) 使用gdb工具运行程序a.out,进入gdb模式 gdb a.out
3) 使用gdb命令
gdb命令 相关释义
l 查看源代码
b 30 设置第30行为断点
info b 查看断点的信息
p val 查看变量 ‘val’ 的值
r 运行
c 继续程序的运行
n 单步运行
q 退出

3. 静态库/动态库的区别、生成和使用?

3.1 库的命令方式?

  • lib[库的名称][.a/.so]

3.2 静态库和动态库的比较?

库的类别 特点/作用
动态库(后缀".so") 动态库不会在编译后包含到可执行代码中,而是在程序运行是才被临时载入,因此程序运行时需要动态库存在,但是程序代码体积较小
静态库(后缀".a") 静态库在编译后被编译到生成的可执行文件中,因此可执行文件体积会庞大,但程序运行时就不需要库。

3.3 制作方法?

1)静态库的制作

步骤 释义
1) 生成目标文件 gcc -c -o test.o test.c
2) 将目标文件编译生成静态库 ar -crs libtest.a test.o

2)动态库的制作

步骤 释义
1) 生成目标文件, 该文件是位置无关代码 gcc -c -fPIC -o test.o test.c
2) 将目标文件编译生成动态库 gcc -shared -o libtest.so test.o

3.4 使用方法?

1)静态库的使用(e.g. libtest.a)

gcc -static -o a.out main.c  -L /usr/lib  -ltest
  • -L : 用于指定库文件的路径
  • -l(小写字母): 用于指定编译时链接的库的名称
  • -static: 选项表示强制使用静态库编译(当有同名同路径的动态库和静态库存在时,系统默认选择动态库)

2)动态库的使用(e.g. libtest.so)

gcc  -o main main.c  -L . -ltest
  • -L: 用于指定库文件的路径
  • -l(小写字母): 用于指定编译时链接的库的名称

3.5 动态库的加载方法?

三种加载方法 示例
方法1 将库移动到 /usr/lib 或者 /lib下
方法2 1) 在LD_LIBRARY_PATH环境变量中加上库所在路径
export LD_LIBRARY_PATH=~/work/lib/
2) 查看环境变量:echo $LD_LIBRARY_PATH
方法3 1) 在/etc/ld.so.conf.d/目录中添加一个文件,文件内容为动态库路径
2) 执行 ldconfig 重新启动配置文件

4. 关键字

4.1 什么是空类型?

空类型是C语言的数据类型中的一种(基本类型、空类型、构造类型);目前空类型分类中只有void。空类型并非无类型,本身也是一种数据结构,常用在类型转换和参数传递的过程中。

1) void

  • 对函数返回值的限定
    • 返回值为void,表示函数没有返回值
  • 对一般变量的限定
    • void* 可以指向任何类型的指针变量(如:int a=0; void* p = &a; p则为“无类型指针,指向变量a的地址”)。
    • 但是不能直接使用void来修饰一个变量:void a;在编译的时候会报错。

4.2 const

const修饰的变量特点
1 const修饰的变量为常变量不是常量
2 const修饰变量时(const int a = 1;等价于int const a = 1;),一旦被初始化后,不能再被直接赋值修改,否则编译器会报错。
3 const 修饰指针本身时(int* const p;),( ‘const’的位置:在 ‘*’ 的右侧,靠近指针变量本身),指针p本身不能被改变。
4 const 修饰指针指向的对象时(int const *p;),( ‘const’的位置:在 ‘*’ 的左侧,远离指针变量本身),指针p指向的内容不能被改变。

4.3 extern

序号 extern作用
1 修饰外部变量 :
1) 防止重复定义
2) 声明(引用)外部变量
2 修饰外部函数(注:可省略)
1) 向编译器声明函数来源外部
2) 可省略:现代编译器自动会在所有参与编译的源文件中查找函数对应的声明
3 修饰C++中调用的C语言函数
1) 向C++编译器声明:参与编译的C函数按照C++编译方式命名

4.4 volatile

volatile 声明变量时,该变量在编译时会“免优化”。

1)变量用volatile声明的含义:

变量值可被某些编译器未知的因素更改,如:操作系统、硬件或者其它线程等。

2)遇到volatile关键字声明的变量:
  • 编译器对访问该变量的代码就不再进行优化;
  • 系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。

4.5 register

register 修饰的变量可存放在cpu寄存器中(变量是否存贮在寄存器中取决于实际寄存器的富余程度,应当尽量减少使用该关键字来修饰变量),便于快速访问那些高频访问的数值。

5. 数组、指针、函数(数组指针、指针数组、函数指针…)

6. 作用域

7. 内存空间分布

一个程序的内存空间分布 释义
stack \|/

heap /|\
1. 为函数内部的局部变量提供存储空间。
2. 进行函数调用时,存储“过程活动记录”。
3. 用作暂时存储区。(如:计算一个很长的算术表达式时,可以将部分计算结果压入堆栈。)
bss BSS (Block Started by Symbol)段
1. 存储未初始化 或 初始化为0的全局变量、静态变量
2. 未初始化的数据并不分配空间,只是记录所需空间大小
3. bss数据段不占用可执行文件的空间,其内容由操作系统初始化(清零)
data 1. 数据段存储经过初始化的全局和静态变量
2. data数据段占用可执行文件的空间
3. data和.bss在加载时合并到一个Segment(Data Segment)中,这个Segment是可读可写的
text 1. 二进制形式的程序代码、函数;
2. 在其中.rodata段也可包含一些只读的常数变量,例如字符串常量等。
3.有些常量并不在只读数据区(如:立即数与指令编译在一起直接放在代码段)
4. 程序加载运行时,.rodata段和.text段通常合并到一个Segment(Text Segment)中,操作系统将这个Segment的页面只读保护起来,防止意外的改写
#include   

int a=1; 			//a, 在数据段(全局已初始化数据区)
char *p;			//p,在BSS(未初始化全局变量)   
  
int main()  
{
	int b; 							//b 在栈区(局部变量)
	char s[]="abc"; 				//s在栈区(局部数组变量); "abc"在常量区(.rodata)  
	char *p1; 						//p1在栈区(局部变量)
	char *p2="123"; 				//p2在栈区,"123"在常量区(.rodata)  
	static int c;	 				//c在bss段; 静态局部变量会自动初始化(因为BSS区自动用0或NULL初始化)  
	p1=(char*)malloc(10); 			//分配得来的10个字节的区域在堆区   
	free(p1);  
	p1=NULL; 						//显式地将p1置为NULL,避免以后错误地使用p1  
} 

8. 内存安全

9. 代码规范

你可能感兴趣的:(C语言基础,C语言面试题,Linux)