之前有写过 日常生活 – 嵌入式面试 ,讲了面试大部分都会问哪些问题。
也有自己总结了一些面试题:
- C语言再学习 – 详解C++/C 面试题 1
- C语言再学习 – 详解C++/C 面试题 2
下载:嵌入式C语言面试题汇总(超经典)
提取码:w779
但是每次我都要翻好几篇文章挨个看知识点,这就很烦了。现在将所用到的知识点在这篇文章内加以总结。
一、关键字
1、const
问题:
- 问题一:const有什么用途?(请至少说明两种)
(1)可以定义const常量
(2)const可以修饰函数的参数和返回值,甚至函数的定义体。被const修饰的东西都受到强制保护,可以预防意外的变动,能提高程序的健壮性。 - 问题二:以下的p和*p哪个可变,哪个不可变?
先忽略类型名(编译器解析的时候也是忽略类型名),我们看 const 离哪个近。“近水楼先得月”,离谁近就修饰谁。
int arr[5];
constint* p = arr; //const 修饰 * p,p 是指针,可变; * p 是指针指向的对象,不可变。intconst * p = arr; //const 修饰 * p,p 是指针, 可变; * p 是指针指向的对象,不可变。int* const p = arr; //const 修饰 p, p 是指针,不可变; p 指向的对象可变。
constint - 问题三:关键字const有什么含义?
const修饰的数据类型是指常类型,常类型的变量或对象的值是不能被更新的。或者说const意味着只读。
解答:
参看:C语言再学习 – 关键字const
const 修饰类型:
(1)const 修饰一般常量
(2)const修饰指针、数组
(3)const 修饰函数的形参和返回值
(4)const 修饰常对象
(5)const 修饰常引用
(6)const 修饰类的成员变量
(7)const 修饰类的成员函数
const 作用:
(1)可以定义 const 常量,具有不可变性。
(2)便于进行类型检查,使编译器对处理内容有更多了解,消除一些隐患。
(3)可以避免意义模糊的数字出现,同样可以很方便进行参数的调整和修改。同宏定义一样,可以做到不变则已,一变都变。
(4)可以保护被修改的东西,防止意外的修改,增强程序的健壮性。
(5)可以节省空间,避免不必要的内存分配。
(6)为函数重载提供了一个参考
(7)提高效率
const介绍:
(1)在定义该const 变量时,通常需要对它进行初始化,因为以后就没有机会再去改变它了;
(2)对指针来说,可以指定指针本身为const,也可以指定指针所指的数据为 const,或二者同时指定为const;
(3)在一个函数声明中,const可以修饰形参,表明它是一个输入参数,在函数内部不能改变其值;
(4)对于类的成员函数,若指定其为const 类型,则表明其是一个常函数,不能修改类的成员变量;
(5)对于类的成员函数,有时候必须指定其返回值为const 类型,以使得其返回值不为“左值”。
作用的话,可以保护被修改的东西,防止意外的修改,增强程序的健壮性。
2、static
问题:
- 问题一:关键static的作用是什么?
static 修饰全局变量
static 修饰局部变量
static 修饰函数
解答:
参看:C语言再学习 – 存储类型关键字
(1)static 修饰的全局变量也叫静态全局变量,该类具有静态存储时期、文件作用域和内部链接,仅在编译时初始化一次。如未明确初始化,它的字节都被设定为0。static全局变量只初使化一次,是为了防止在其他文件单元中被引用;利用这一特性可以在不同的文件中定义同名函数和同名变量,而不必担心命名冲突。
(2)static 修饰的局部变量也叫静态局部变量,该类具有静态存储时期、代码作用域和空链接,仅在编译时初始化一次。如未明确初始化,它的字节都被设定为0。函数调用结束后存储区空间并不释放,保留其当前值。
(3)static 修饰的函数也叫静态函数,只可以在定义它的文件中使用。
3、volatile
问题:
- 问题一:关键字volatile有什么含意?并给出三个不同的例子。
volatile关键字是一种类型修饰符。volatile的作用是作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值。直接读值是指从内存重新装载内容,而不是直接从寄存器拷贝内容。
volatile使用:
(1)并行设备的硬件寄存器(如:状态寄存器)。
(2)一个中断服务子程序中会访问到的非自动变量。
(3)多线程应用中被几个任务共享的变量。
解答:
参看:C语言再学习 – 关键字volatile
4、sizeof
问题:
- 问题一:以下为Windows NT下的32位C++程序,请计算sizeof的值。
void Func ( char str[100])
{
请计算:
sizeof( str ) = 4
}
char str[] = “Hello” ;
char *p = str ;
int n = 10;
请计算:
sizeof (str ) = 6
sizeof ( p ) = 4
sizeof ( n ) = 4
void *p = malloc( 100 );
请计算:
sizeof ( p ) = 4 - 问题二:在 32 位系统下:short * p =NULL;sizeof( p )的值是多少?sizeof( * p)呢?
sizeof ( p ) = 4; 因为 p为指针,32位系统 指针所占字节为 4个字节
sizeof ( *p ) = 2; 因为 *p 为 指针所指向的变量为int类型,short为 2个字节 - 问题三:请计算sizeof的值
int a[100];
sizeof (a) = 400; // 因为 a是类型为整型、有100个元素的数组,所占内存为400个字节
sizeof (a[100]) = 4; //因为 a[100] 为数组的第100元素的值该值为 int 类型,所占内存为4个字节。
sizeof (&a) = 4; //因为 &a 为数组的地址即指针,32位系统 指针所占字节为 4个字节
sizeof (&a[0]) = 4; //因为&a[0] 为数组的首元素的地址即指针,32位系统 指针所占字节为 4个字节
解答:
参看:C语言再学习 – 关键字sizeof与strlen
(1)sizeof 操作符以字节形式给出了其操作数的存储大小。操作数可以是一个表达式或括在括号内的类型名。操作数的存储大小由操作数的类型决定。
在windows,32位系统中
char 1个字节
short 2个字节
int 4个字节
long 4个字节
double 8个字节
float 4个字节
(2)数据类型必须用圆括号括住。如:sizeof (int)
记住这两句话:
在 32 位系统下,不管什么样的指针类型,其大小都为 4 byte。
参数传递数组永远都是传递指向数组首元素的指针。
5、extern
问题:
- 问题一:在C++程序中调用被C编译器编译后的函数,为什么要加extern “C”声明?
C++语言支持函数重载, C 语言不支持函数重载。函数被 C++编译后在库中的名字与 C 语言的不同。假设某个函数的原型为: void foo(int x, int y);该 函 数 被 C 编 译 器 编 译 后 在 库 中 的 名 字 为 _foo, 而 C++编 译 器 则 会 产 生 像_foo_int_int 之类的名字。C++提供了 C 连接交换指定符号 extern“ C”来解决名字匹配问题。
解答:
参看:C语言再学习 – 存储类型关键字
C 程序中,不允许出现类型不同的同名变量。而C++程序中 却允许出现重载。重载的定义:同一个作用域,函数名相同,参数表不同的函数构成重载关系。因此会造成链接时找不到对应函数的情况,此时C函数就需要用extern “C”进行链接指定,来解决名字匹配问题。简单来说就是,extern “C”这个声明的真实目的是为了实现C++与C及其它语言的混合编程。
二、字符串
1、strcpy 函数功能实现
问题:
- 问题一:编写 strcpy 函数
已知 strcpy 函数的原型是
char *strcpy(char *strDest, const char *strSrc);
其中 strDest 是目的字符串, strSrc 是源字符串。
(1)不调用 C++/C 的字符串库函数,请编写函数 strcpy
char *strcpy(char *strDest, const char *strSrc);
{
assert((strDest!=NULL) && (strSrc !=NULL)); // 2分
char *address = strDest; // 2分
while( (*strDest++ = * strSrc++) != ‘\0’ ) // 2分
NULL ;
return address ; // 2分
}
(2) strcpy 能把 strSrc 的内容复制到 strDest,为什么还要 char * 类型的返回值?
答:为了实现链式表达式。 // 2 分
例如 int length = strlen( strcpy( strDest, “hello world”) );
2、字符串的翻转
问题:
- 问题一:字符串的翻转实现
实现逻辑,就是将字符串从中间一分为二,互相换位置即完成了翻转的效果
void rechange_str(char *str)
{
int i, len;
char tmp;
if (NULL == str) {
return ;
}
len = strlen(str);
for (i = 0; i < len/2; i ++) {
tmp = str[i];
str[i] = str[len-i-1];
str[len-i-1] = tmp;
}
}
3、strcpy和memcpy区别
问题:
- 问题一:strcpy和memcpy区别?
(1)复制的内容不同。strcpy只能复制字符串,而memcpy可以复制任意内容,例如字符数组、整型、结构体、类等。
(2)复制的方法不同。strcpy不需要指定长度,它遇到被复制字符的串结束符"\0"才结束,所以容易溢出。memcpy则是根据其第3个参数决定复制的长度。
(3)用途不同。通常在复制字符串时用strcpy,而需要复制其他类型数据时则一般用memcpy。
解答:
参看:C语言再学习 – 字符串和字符串函数
剩下的像strcat、strlen、atoi、itoa函数功能实现,自行查看。
三、大小端
问题:
- 问题一:C语言实现大小端
#include <stdio.h>
#icnlude <arpa/inet.h>
int main (void)
{
union
{
short i;
char a[2];
}u;
u.a[0] = 0x11;
u.a[1] = 0x22;
printf ("0x%x\n", u.i); //0x2211 为小端 0x1122 为大端
printf ("0x%.x\n", htons (u.i)); //大小端转换
return 0;
}
输出结果:
0x2211
0x1122
- 问题二:大小端应用场景?
一般操作系统都是小端,而通讯协议是大端的。
大小端是由CPU和操作系统来决定的,在操作系统中,x86和一般的OS(如windows,FreeBSD,Linux)使用的是小端模式,但比如Mac OS是大端模式。
在网络上传输数据时,由于数据传输的两端对应不同的硬件平台,采用的存储字节顺序可能不一致。所以在TCP/IP协议规定了在网络上必须采用网络字节顺序,也就是大端模式。
解答:
参看:C语言再学习-- 大端小端详解(转)
一般都是采用 union 来判断机器的字节序。
union 型数据所占的空间等于其最大的成员所占的空间。 对 union 型的成员的存取都是相对于该联合体基地址的偏移量为 0 处开始,也就是联合体的访问不论对哪个变量的存取都是从 union 的首地址位置开始。
联合是一个在同一个存储空间里存储不同类型数据的数据类型。 这些存储区的地址都是一样的,联合里不同存储区的内存是重叠的,修改了任何一个其他的会受影响。
现在明白了,我们为什么用 union 联合来测试大小端,在联合变量 u 中, 短整型变量 i 和字符数组 a 共用同一内存位置。给 a[0]、a[1] 赋值后,i 也是从同一内存地址读值的。
四、预处理
问题:
- 问题一:用预处理指令#define 声明一个常数,用以表明1年中有多少秒(忽略闰年问题)
#define SENCONDS_PER_YEAR (60 * 60 * 24 * 365)UL
#define 声明一个常量,使用计算常量表达式的值来表明一年中有多少秒,显得就更加直观了。再有这个表达式的值为无符号长整形,因此应使用符号 UL。 - 问题二:写一个“标准”宏MIN ,这个宏输入两个参数并返回较小的一个。
#define MIN(A,B) ((A) <= (B) ? (A) : (B))
实现输入两个参数并返回较小的一个,应使用三目表达式。使用必须的足够多的圆括号来保证以正确的顺序进行运行和结合。 - 问题三:#include <filename.h>和#include “filename.h”有什么区别?
对于#include <filename.h> ,编译器从标准库路径开始搜索 filename.h
对于#include “filename.h” ,编译器从用户的工作路径开始搜索 filename.h - 问题四:计算值?
#define N 3
#define Y(n) ((N+1)*n)
执行语句z=2*(N+Y(5+1));
变量z的值为(B ).
A:42 B:48 C:54 D:出错
2*(N+Y(5+1)); =》 2*(3+(3+1)*5+1) =》 48
若有宏定义:#define MOD(x,y) x%y
则执行以下语句后的输出结果是
int a=13,b=94;
printf(″%d\n″,MOD(b,a+4));
A.5
B.7
C.9
D.11
所以实际上计算的结果是MOD(b,a+4),即printf(″%d\n″,b%a+4);b%a=3,所结果是3+4=7
解答:
参看:C语言再学习 – C 预处理器
使用#define需要注意下面几点:
(1)宏的名字中不能有空格,但是在替代字符串中可以使用空格。ANSI C 允许在参数列表中使用空格。
(2)用圆括号括住每个参数,并括住宏的整体定义。
(3)用大写字母表示宏函数名,便于与变量区分。
(4)有些编译器限制宏只能定义一行。即使你的编译器没有这个限制,也应遵守这个限制。
(5)宏的一个优点是它不检查其中的变量类型,这是因为宏处理字符型字符串,而不是实际值。
(6)在宏中不要使用增量或减量运算符。
五、位操作
问题:
- 问题一:嵌入式系统总是要用户对变量或寄存器进行位操作。给定一个整型变量a,写两段代码,第一个设置a的bit 3,第二个清除a 的bit 3。在以上两个操作中,要保持其它位不变。
#define BIT3 (0x1 << 3)
static int a;
void set_bit3 (void)
{
a |= BIT3;
}
void clear_bit3 (void)
{
a &= ~BIT3;
}
- 问题二:不用临时变量交换两个数的值
通过按位异或实现:
#include <stdio.h>
int main(int argc, char *argv[])
{
int a = 2, b = 6;
a = a ^ b;
b = b ^ a;
a = a ^ b;
printf("a = %d b = %d/n", a, b);
return 0;
}
结果如下:
a = 6 b = 2
- 或者:
#include <stdio.h>
int main(int argc, char *argv[])
{
int a = 2, b = 6;
a = a + b;
b = a - b;
a = a - b;
printf("a = %d b = %d/n", a, b);
return 0;
}
结果如下:
a = 6 b = 2
解答:
参看:C语言再学习 – 位操作
六、编译
问题:
- 问题一:GCC编译过程?
整个过程中可以划分为以下的4步流程:
(1)预处理/预编译: 主要用于包含头文件的扩展,以及执行宏替换等 //加上 -E
(2)编译:主要用于将高级语言程序翻译成汇编语言,得到汇编语言 //加上 -S
(3)汇编:主要用于将汇编语言翻译成机器指令,得到目标文件 //加上 -c
(4)链接:主要用于将目标文件和标准库链接,得到可执行文件 //加上 -o - 问题二:交叉编译和GCC编译有什么区别?
本地编译:在当前编译平台下编译出来的程序只能在当前平台下运行。
交叉编译:在当前编译平台下,编译出来的程序能运行在体系结构不同的另一种目标平台上,但是编译平台本身却不能运行该程序。
gcc编译:
#file libmosquitto.so.1
libmosquitto.so.1: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked,
BuildID[sha1]=0xb05795243eded68d65abe39b12212099c849965b, not stripped
arm-none-linux-gnueabi-gcc 交叉编译:
#file libmosquitto.so.1
libmosquitto.so.1: ELF 32-bit LSB shared object, ARM, version 1 (SYSV), dynamically linked, not stripped
解答:
参看:C语言再学习 – GCC编译过程
七、栈溢出
问题:
- 问题一:栈溢出产生原因?
(1)局部数组变量空间太大
(2)函数出现无限递归调用或者递归层次太深 - 问题二:栈溢出解决方法?
(1)动态内存分配
(2)增大栈空间
(3)找到使用递归的地方,并消除BUG
解答:
参看:C语言再学习 – Stack Overflow(堆栈溢出)
八、指令
问题:
- 问题一:如何在目录下查找一个文件?
find . -name test.c - 问题二:如何列出匹配的文件名?
grep -l ‘main’ *.c
将列出当前目录下所有以”.c”结尾且文件中包含’main’字符串的文件名。
解答:
参看:C语言再学习 – Linux下find命令用法
参看:C语言再学习 – grep 命令(转)
其他指令:
参看:UNIX再学习 – ps、top、kill 指令
参看:C语言再学习 – Xargs用法详解
参看:C语言再学习-- readelf、objdump、nm使用详解
参看:C语言再学习 – dmesg 命令
参看:C语言再学习 – Linux 中常用基本命令
参看:C语言再学习 – vim常用快捷键(转)
参看:C语言再学习 – 常用快捷键
九、数据结构及算法
问题:
- 问题一:二分查找、冒泡排序、快速排序、单链表插入、时间复杂度
- 问题二:什么是平衡二叉树?
左右子树都是平衡二叉树 且左右子树的深度差值的绝对值不大于1。 - 问题三:冒泡排序算法的时间复杂度是什么?
O(n^2)
用大 O 记号表示算法的时间性能,将基本语句执行次数的数量级放入大 O 记号中。
大 O 中的 O 的意思就是"order of"(大约是)。
解答:
参看:数据结构与算法 – 算法
十、进程
问题:
- 问题一:什么是进程,进程与程序的区别?
解答:
参看:UNIX再学习 – 进程环境
1、进程的概念主要有两点:
第一,进程是一个实体。 每一个进程都有它自己的地址空间,一般情况下,包括文本区域(text region)、数据集区域(data region)和堆栈(stack region)。文本区域存储处理执行的代码;数据区域存储变量和基础讷航执行期间使用的动态分配的内存;堆栈区域存储着活动过程调用的指令和本地变量。
第二,进程是一个“执行中的程序”。 程序是一个没有生命的实体,只有处理器赋予程序生命时(操作系统执行它),它才能成为一个活动的实体,我们称其为进程。
2、程序和进程的区别:
而程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。而进程是程序在处理机上的一次执行过程,它是一个动态的概念。程序可以作为一种软件资料长期存在,而进程是一定生命周期的。程序时永久的,进程是暂时的。进程更能真是地描述并发,而程序不能。进程具有创建其他进程的功能,而程序没有。同一个程序同时运行于若干个数据集合上,它将属于若干个不同的进程,也就是说同一个程序可以对应多个进程。在传统的操作系统中,程序并不能独立运行,作为资源分配和独立运行的基本单元都是进程。
3、进程的特征
动态性: 进程的实质是程序在多道程序系统中的一次执行过程,进程是动态产生,动态消亡的。
并发性: 任何进程都可以同其他进程一起并发执行。
独立性: 进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位。
异步性: 由于进程间的相互制约,使进程具有执行的间断性,即进程按各自独立的,不可预知的速度向前推进。
结构特征: 进程有程序、数据、和进程控制块三部分组成。
多个不同的进程可以包含相同的程序: 一个程序在不同的数据集里就构成不同的进程,能得到不同的结果;但是执行过程中,程序不能发生改变。
4、进程终止
有 8 种方式使进程终止,其中 5 种为正常终止,它们是:
(1)从 main 返回
(2)调用 exit
(3)调用 _exit 或 _Exit
(4)最后一个线程从其启动例程返回
(5)从最后一个线程调用 pthread_exit
异常终止有 3 种方式,它们是:
(6)调用 abort
(7)接到一个信号
(8)最后一个线程对取消请求做出响应。
十一、线程
问题:
- 问题一:什么是线程,进程与线程的的关系?
解答:
参看:UNIX再学习 – 线程
线程和进程的关系:
(1)一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个主线程。它们共享进程的地址空间;而进程有自己独立的地址空间。
(2)同一进程内的所有线程共享该进程的所有资源。
(3)线程作为调度和分配的基本单位,进程作为拥有资源的基本单位。
(4)进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源。
(5)在创建或撤销进程时,由于系统要为之分配和回收资源,导致系统的开销大于创建或撤销线程时的开销。
(6)不仅进程之间可以并发执行,同一个进程的多个线程之间也可并发执行。
十二、线程同步
问题:
- 问题一:线程同步有几种方法?
线程同步方式:互斥量、读写锁、条件变量、自旋锁。 - 问题二:什么是死锁?
所谓死锁: 是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。 - 问题三:死锁的四种产生条件是什么?
虽然进程在运行过程中,可能发生死锁,但死锁的发生必须具备一定的条件,死锁的发生必须具有以下四个必要条件。
(1)互斥条件
指进程对所分配的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用完释放。
(2)请求和保持条件
只进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。
(3)不剥夺条件
只进程已获得的资源,在未使用之前,不能被剥夺,只能在使用完时由自己释放。
(4)环路等待条件
只在发生死锁时,必然在一个进程 – 资源的环形链,即进程集合{P0, P1, P2 …, Pn} 中的 P0 正在等待一个 P1 占用的资源;P1 正在等待 P2 占用的资源,…,Pn正在等待已被 P0 占用的资源。 - 问题四:如何避免死锁?
在有些情况下死锁是可以避免的。三种用于避免死锁的技术:
(1)加锁顺序(线程按照一定的顺序加锁)
(2)加锁时限(线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁)
(3)死锁检测
解答:
参看:UNIX再学习 – 线程同步
十三、进程间通信
问题:
- 问题一:进程间通信有几种方式?
进程间通信 (IPC)方式有:
(1)管道
(2)消息队列
(3)信号量
(4)共享存储
(5)套接字(socket)
其中消息队列、信号量、共享存储统称为 XSI IPC通信方式。
解答:
参看:UNIX再学习 – 进程间通信之管道
参看:UNIX再学习 – XSI IPC通信方式
参看:UNIX再学习 – 网络IPC:套接字
简单介绍以下 :
信号量:广泛用于进程或线程间的同步和互斥
消息队列:是消息的链表,存放在内存中,由内核维护
管道:最简单、数据只能读取⼀次半双⼯、匿名管道只能是有⾎缘的关系间通信:
命名管道:⽤于没有⾎缘关系之间的进程间通信
共享内存:效率⾼、不需要太多次的数据拷⻉,可以直接进⾏读写,缺点是不能保证数据同步,只能借助信号量保证同步
信号:简单、携带的信息量少,使⽤在特定的场景,优先级⾼。建议不要使⽤信号进⾏进程间通信,因为信号的优先级⾼会打破原有进程的执⾏过程
socket:主要⽤于⽹络中的进程间通信,通信过程以及数据复杂,但安全可靠。
十四、TCP和UDP
问题:
- 问题一:TCP和UDP的区别?
(1)UDP 不提供客户机与服务器的连接,而TCP 提供客户机与服务器的连接;
(2)UDP 不保证数据传输的可靠性和有序性,而TCP 保证数据传输的可靠性和有序性;
(3)UDP 不提供流量控制,而TCP 提供流量控制;
(4)UDP 是记录式传输协议,而TCP 是流式传输协议;
(5)UDP 是全双工的,TCP 也是全双工的。
解答:
参看:UNIX再学习 – TCP/UDP 客户机/服务器
十五、TCP协议中的三次握手和四次挥手
问题:
- 问题一:TCP协议中的三次握手和四次挥手过程?
解答:
参看:TCP协议中的三次握手和四次挥手(图解)
三次握手:
- (1)首先Client端发送连接请求报文,
- (2)Server段接受连接后回复ACK报文,并为这次连接分配资源。
- (3)Client端接收到ACK报文后也向Server段发送ACK报文,并分配资源,
这样TCP连接就建立了。
四次挥手:
- (1)假设Client端发起中断连接请求,也就是发送FIN报文。
- (2)Server端接到FIN报文后,意思是说"我Client端没有数据要发给你了",但是如果你还有数据没有发送完成,则不必急着关闭Socket,可以继续发送数据。所以你先发送ACK,“告诉Client端,你的请求我收到了,但是我还没准备好,请继续你等我的消息”。这个时候Client端就进入FIN_WAIT状态,继续等待Server端的FIN报文。
- (3)当Server端确定数据已发送完成,则向Client端发送FIN报文,“告诉Client端,好了,我这边数据发完了,准备好关闭连接了”。
- (4)Client端收到FIN报文后,"就知道可以关闭连接了,但是他还是不相信网络,怕Server端不知道要关闭,所以发送ACK后进入TIME_WAIT状态,如果Server端没有收到ACK则可以重传。“,Server端收到ACK后,“就知道可以断开连接了”。Client端等待了2MSL后依然没有收到回复,则证明Server端已正常关闭,那好,我Client端也可以关闭连接了。
Ok,TCP连接就这样关闭了!
【问题1】为什么连接的时候是三次握手,关闭的时候却是四次握手?
- 答:因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,“你发的FIN报文我收到了”。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。
【问题2】为什么TIME_WAIT状态需要经过2MSL(最大报文段生存时间)才能返回到CLOSE状态?
- 答:虽然按道理,四个报文都发送完毕,我们可以直接进入CLOSE状态了,但是我们必须假象网络是不可靠的,有可以最后一个ACK丢失。所以TIME_WAIT状态就是用来重发可能丢失的ACK报文。
十六、ISO/OSI 网络协议模型
问题:
- 问题一:什么是 ISO/OSI 网络协议模型?
解答:
参看:UNIX再学习 – 网络与网络协议
主要关注:
应用层:HTTP/FTP等
传输层:TCP/UDP协议
网络层:IP协议 ICMP协议
MQTT:使用TCP/IP提供基础网络连接。
十七、linux启动流程
问题:
- 问题一:介绍一下linux启动流程?
解答:
参看:S5PV210开发 – 启动流程
iROM启动流程:
S5PV210启动过程分为BL0、BL1、BL2三个阶段,S5PV210内部有96Kb的IRAM和64Kb的IROM。S5PV210启动过程如下图:
注释:其中 BL1 最大 16KB,BL2 最大 80KB
第一步:iROM初始化,初始化系统时钟、特殊设备控制寄存器和启动设备
第二步:iROM启动代码加载BL1(bootloader)到iRAM,在安全启动模式下iROM对BL1进行整体校验。
第三步:执行BL1,BL1加载BL2(剩余的bootloader)到iRAM,BL1将会对BL2进行整体校验。
第四步:执行BL2,BL2初始化SDRAM控制器,将OS下载到SDRAM
第五步:跳转到OS起始地址,进入系统。
注释:
iROM 是 Internal Read-Only Memory 的缩写,即 内部只读存储器。
RAM 是 Random-Access Memory 的缩写,即 随机存取存储器,数据掉电丢失。
十八、内存管理
问题:
- 问题一:全局变量和局部变量在内存中的区别?
全局变量保存在内存的全局存储区中,占用静态的存储单元;局部变量保存在栈中,只有在所在函数被调用时才动态地为变量分配存储单元。
(没有说是静态全局和静态局部哦,否则就不是这种分布了)。 - 问题二:堆和栈的区别?
(1)堆栈空间分配区别
栈(操作系统):由操作系统(编译器)自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
堆(操作系统): 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收,分配方式倒是类似于链表。
(2)堆栈缓存方式区别
栈使用的是一级缓存, 它们通常都是被调用时处于存储空间中,调用完毕立即释放。
堆则是存放在二级缓存中,生命周期由虚拟机的垃圾回收算法来决定(并不是一旦成为孤儿对象就能被回收)。所以调用这些对象的速度要相对来得低一些。
(3)堆栈数据结构区别
堆(数据结构):堆可以被看成是一棵树,如:堆排序。
栈(数据结构):一种先进后出的数据结构。
解答:
参看:UNIX再学习 – 内存管理
存储空间布局:
历史沿袭至今,C 程序一直由下列几部分组成:
(1)正文段( text段/代码段)
这是由 CPU 执行的机器指令的部分。通常,正文段是可共享的,所以即使是频繁执行的程序(如文本编辑器,C 编译器和 shell 等)在存储器中也只需有一个副本,另外正文段常常是只读的,以防止程序由于意外而修改其指令。
正文段是用来存放可执行文件的操作指令,也就是说它是可执行程序在内存中的镜像。
(2)初始化数据段(数据段)
数据段用来存放可执行文件中已经初始化的全局变量,换句话说就是存放程序静态分配的变量和全局变量。
例如,C 程序中任何函数之外的声明:
int num = 10;
使此变量以其初值存放在初始化数据段中。
(3)未初始化数据段(bbs段)
bbs段包含了程序中未初始化的全局变量,在程序开始执行之前,内核将此段中的数据初始化为 0 或空指针。
例如:函数外的声明:
long sum[100];
使此变量存放在非初始化数据段中。
(4)堆
堆是用于存放进程进行中被动态分配的内存段,它大小并不固定,可动态扩张或缩减。当进程调用 malloc 等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);当利用 free 等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)。 由于历史上形成的惯例, 堆位于未初始化数据段和栈之间 。
(5)栈
栈是用户存放程序临时创建的局部变量,也就是说我们函数括弧“{}”中定义的变量(但不包括static 声明的变量,static 意味着在数据段中存放变量)。除此之外在函数调用结束后,函数的返回值也会被存放回栈中。由于栈的后进先出(LIFO)特点,所以栈特别方便用来保存/恢复调用现场。从这个意义上讲我们可以把堆栈看成一个临时数据寄存,交换的内存区。
(6)参数和环境区
命令行参数和环境变量。
十九、linux下的⽬录结构
问题:
- 问题一:简单描述⼀下linux下的⽬录结构 ?
- 问题二:根目录用什么符号表示,当前目录、上层目录用什么符号表示 ?用户目录用什么符号表示?切换目录用什么指令?
根目录:/
当前目录和上层目录: ./ …/
用户目录:home
切换目录: cd
解答:
参看:Hi3516A开发–目录分析
/…根目录
bin …基本命令的可执行文件
boot …内核映像已经启动时需要用到的一些文件
dev …设备文件
etc …系统配置文件,包括启动文件
home …用户目录
lib …基本库,例如C库和内核模块
lost+found …在文件系统修复时恢复的文件
mnt …临时文件系统的挂载点
nfsroot …nfs文件夹,一般不使用
opt …添加的软件包
proc …内核以及进程信息的虚拟文件系统
root …root用户目录
sbin …用于系统管理的可执行程序
share …共享文件目录
sys …系统设备和文件层次结构,向用户提供详细的内核数据信息
tmp …临时文件
usr …该目录的二级目录包含许多对用户很有用的应用程序和文档
var …存放系统日志或一些服务程序的临时文件
二十、数组和指针
问题:
- 问题一:用变量a给出下面的定义 ?
a)一个整型数
int a;
b)一个指向整型数的指针
int *a;
c)一个指向指针的的指针,它指向的指针是指向一个整型数
int **a;
d)一个有10个整型数的数组
int a[10];
e)一个有10个指针的数组,该指针是指向一个整型数的。
int *a[10];
f)一个指向有10个整型数数组的指针
int (*a)[10];
g)一个指向函数的指针,该函数有一个整型参数并返回一个整型数
int (*a)(int);
h)一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数
int (*a[10])(int);
解答:
参看:C语言再学习 – 再论数组和指针
二十一、内联函数(inline关键字)
问题:
- 问题一:
解答:
二十二、修改linux文件权限
问题:
- 问题一:chmod 755 test 是什么意思?
文件属主(u)可读/可写/可执行权;
与文件主同组用户(g) 可读/可执行;
其他人(o) 可读/可执行。
解答:
参看:C语言再学习 – 修改linux文件权限
chmod命令用于改变文件或目录的访问权限。
数字表示的属性的含义:0表示没有权限,1表示可执行权限,2表示可写权限,4表示可读权限,然后将其相加。所以数字属性的格式应为3个从0到7的八进制数,其顺序是(u)(g)(o)。
u 表示“用户(user)”,即文件或目录的所有者。
g 表示“同组(group)用户”,即与文件属主有相同组ID的所有用户。
o 表示“其他(others)用户”。
chmod 755 test,即设定test这个文件的属性为:
文件属主(u)可读/可写/可执行权
与文件主同组用户(g) 可读/可执行
其他人(o) 可读/可执行。
二十三、优先级
问题:
- 问题一:算术运算符,赋值运算符和关系运算符的运算优先级按从高到低依次为()?
算术运算符 > 关系运算符 > 赋值运算符
解答:
参看:C语言再学习 – 运算符与表达式
简单记就是:
! > 算术运算符 > 关系运算符 > && > || > 赋值运算符
二十四、多路复用
问题:
- 问题一:介绍一下select和epoll?
解答:
参看:UNIX再学习 – 函数 select、poll、epoll
select常见的程序片段:
fs_set readset;
struct timeval timeout;
timeout.tv_sec = 5;
timeout.tv_usec = 0;
FD_ZERO(&readset);
FD_SET(fd,&readset);
select(fd+1,&readset,NULL,NULL, &timeout);
if(FD_ISSET(fd,readset){……}
(1)调用 FD_ZERO 将一个 fd_set 变量的所有位设置为 0。
(2)要开启描述符集中的一位,可以调用 FD_SET。
(3)调用 FD_CLR 可以清除一位。
(4)通过 select 返回值,做判断语句。负值,select 错误;正值,某些文件可读、写或异常;0,等待超时,没有可读、写或异常的文件。
timeout 的取值有以下 3 中情况:
timeout == NULL, 永远等待。如果捕捉到一个信号则中断此无限期等待。
timeout->tv_sec == 0 && timeout->tv_usec == 0 ,根本不等待。
timeout->tv_sec != 0 || timeout->tv_usec != 0, 等待指定的秒数和微妙数。
(5)最后,可以调用 FD_ISSET 测试描述符集中的一个指定位是否已打开。
二十五、自增和自减++和–
问题:
- 问题一:请写出下列代码的输出内容?
main()
{
int a,b,c,d;
a=10;
b=a++; /*先调用a, 再递增a, 调用时 a的值为10(b的值为10), 调用后 a的值为11*/
c=++a;/*先递增a,再调用a,调用时 a的值为12(c的值为12),调用后 a的值为12*/
d=10*a++; /*先调用a, 再递增a, 调用时 a的值为12(d的值为120), 调用后 a的值为13*/
printf("b,c,d:%d, %d,%d",b,c,d);
return 0;
}
输出为:
b,c,d:10, 12,120
#include <stdio.h>
int main()
{
int i = 6;
int j = 1;
if(i>0||(j++)>0);
printf("%D\r\n",j);
return 0;
}
输出结果为1。
解答:
只需要记住:
后缀: a++; a–; 使用a的值之后改变a;
前缀: ++a; --a; 使用a的值之前改变a;
二十六、对齐补齐
问题:
- 问题一:某32位嵌入式系统采用4字节对齐在定义一个该变量时,所占内存空间大小是 多少?
typedef struct{
short id;//4
int lun;//4
double reserved;//8
char str[10];//12
}Drive;
sizeof(Drive)= 28;
注意: 是在32位嵌入式系统,如果是64位嵌入式系统,结果就不一样了,会是32。
如何判断Linux是32位还是64位:
方法一:getconf LONG_BIT
在linux终端输入 getconf LONG_BIT 命令
如果是32位机器,则结果为32
[root@localhost ~]# getconf LONG_BIT
32
如果是64位机器,则结果为64
[root@localhost ~]# getconf LONG_BIT
64
二十七、unsigned
问题:
- 问题一:下面一段程序中,while循环执行了多少次后退出循环?
unsigned char n = 10;
int total;
while (n-->=0)
{
total += n;
}
unsigned 无符号,n不会小于等于0,则无限循环。
二十八、中断
问题:
- 问题一:简述嵌入式系统产生中断后,CPU响应中断大致分为几个阶段?