笔试题的问题

sizeof和strlen的区别
strlen计算字符串的长度,以’\0’为字符串结束标志
sizeof是分配的数组实际所占的内存空间大小,不受里面存储内容
例如
char *str = “1111111abcd”;
char str1[9] = {1};
sizeof(str) = 4; //是指指针所占的字节大小,在c/c++中一个指针占4个字节(32位系统)
sizeof(str1) = 9;
sizeof()是运算符,由于在编译时计算,因此sizeof不能用来返回动态分配的内存空间的大小。实际上,用sizeof来返回类型以及静态分配的对象、结构或数组的空间,返回值跟这些里面所存储的内容没有关系。
具体而言,当参数分别如下时,sizeof返回的值含义如下:
数组-编译时分配的数组空间大小
指针-存储该指针所用的空间大小
类型-该类型所占的空间的大小
对象-对象的实际占用空间大小
函数-函数返回类型所占空间的大小
strlen()是函数,可以计算字符串的长度,直到遇到结束符NULL才结束,返回的长度大小不包含NULL
ps在这里插入图片描述

32位
(正确26)sizeof有算“\0”
4(64位8)
(正确4 int4个字节)
(正确4 64位8)str当参数传输会退化成指针
(正确4 64位8)只能测定 指针大小,32位机上得4。
sizeof 不能测定动态分配的数组大小
sizeof是分配数组的实际所占的内存空间大小,不受储存内容所影响
strlen是计算字符串大小的,以”\0“为结束符

冒泡法
int str[8]={38,11,9,28,2,56,10,2};
int i,j;
int tmp;
for(i=0;i<8-1;i++){
for(j=0;j<7-i;j++){
if(str[j]>str[j+1]){
tmp=str[j];
str[j]=str[j+1];
str[j+1]=tmp;
}
}
}
第1次冒泡排序结果:3,2,1,4(红色字体固定,下次不用再参加排序)
第2次冒泡排序结果:2,1,3,4(红色字体固定,下次不用再参加排序)
第3次冒泡排序结果:1,2,3,4(红色字体固定,下次不用再参加排序)
经过3次冒泡排序已经完成了排序。因为第3次中“2"已经固定,下次不用再参加排序。那“1”不再有相邻的值与它排序比较,则排序结束。数组长度为4,只需要循环比较3次即可。

strcpy函数和溢出问题
char *mystrcpy(char *dst, char *src){
int i;
char *temp=dst;//保存首地址
//判断条件
while(src!=’\0’){
*dst++=*src++;
}
//给一个‘\0’
*dst=’\0’;
return temp;
}
详细说明strcpy:https://www.cnblogs.com/chenyg32/p/3739564.html

例子:
char s[]=“123456789”;
char d[]=“123”;
strcpy(d,s);
printf(“result: %s, \n%s”,d,s);
结果:
在这里插入图片描述
以上是gcc编译器输出结果。
定义了s和d之后,d存在s之前(在栈上,先进后出),为:
1 2 3 \0 1 2 3 4 5 6 7 8 9 \0
d s
此时,strcpy将s复制到d之中,有:
1 2 3 4 5 6 7 8 9 \0 7 8 9 \0
d s
因此,输出d时,会有123456789,输出s时有56789.
总结:
这种将长字符串赋值给短字符串是不允许的,会造成内存溢出,访问不该访问的地方,造成无法预料的结果。因此,复制之前,确保目的字符串 能够 保存 源字符串。

异或

代码问题:
#include <stdio.h>
int main()
{
char x=0xFF;
printf("%d\n",x–);
printf("%d\n",x);
return 0;
}

结果:-1 -2
0xFF 二进制:1111 1111 第一个1为符号位 ,加一取反1111 1110 0000 0001
符号位1 1000 0001 x=-1

以%d形式输出,oxffff = 1111 1111 1111 1111,最高位为1,说明是负数。
对应的正数的原码 = ~(负数的源码 + 1)。(~取反)
所以加一为1111 1111 1111 1110,
取反就是0000 0000 0000 0001。也就是对应的正数原码是1。所以x = -1呗。


#include<stdio.h>
union{ int i;char x[2]; }a;
void main()
{ a.x[0]=10;
a.x[1]=1;
printf("%d",a.i);
存储的时候是“低位在前”,a[0]是00001010,a[1]是00000001
输出的i是将二个字节作为一个整数看,即是x[1]x[0]也就是00000001 00001010
即 256+8+2=266。

对于一个无符号字节数据,如何获取其高5位和低三位?写出计算过程
byte by=19;
byte byHigh5=?
byte byLow3=?

byHigh=(19&0xf8)>>3

#include <stdio.h>
2 int main()
3 {
4 unsigned char byte;
5 byte=19;
6 unsigned char lo4,hi4;
7 hi4 = (byte &0xf8) >> 3;
8 lo4 = byte & 0x07;
9 printf("%i\n",hi4);
10 printf("%i\n",lo4);
11 return 0;
12 }

结果:2 3

strstr(“abc”,“b”)输出c
/*
strstr 实现作用:在s1中查找s2子串
返回s2在s1中第一次出现的地址,如果没找到则返回NULL。
匹配过程不包括NULL字符。
第二种方法 朴素的模式匹配算法 ,只用一个外层循环
*/

char* mystrstr1(const char* dest, const char* src) {
char* tdest = dest;
char* tsrc = src;
int i = 0;//tdest 主串的元素下标位置,从下标0开始找,可以通过变量进行设置,从其他下标开始找!
int j = 0;//tsrc 子串的元素下标位置
while (i <= strlen(tdest) - 1 && j <= strlen(tsrc)-1)
{
if (tdest[i] == tsrc[j])//字符相等,则继续匹配下一个字符
{
i++;
j++;
}
else//在匹配过程中发现有一个字符和子串中的不等,马上回退到 下一个要匹配的位置
{
i = i - j + 1;
j = 0;
}
}
//循环完了后j的值等于strlen(tsrc) 子串中的字符已经在主串中都连续匹配到了
if (j == strlen(tsrc))
{
return tdest + i - strlen(tsrc);
}

return NULL;

}

有一行错误,改出来
#include <stdio.h>
2 int main()
3 {
4 int *p;
5 int i;
6 int a[10];
7 p=a;
8 for(i=0;i<10;i++){
9 *p++=i;
10 }
11 for(i=0;i<10;i++){
12 printf(“a[%d]=%d\n”,i, *p++);// *(p-10+i)如果直接p++会造成溢出
13 }
14 }

全局变量
函数外面声明
可以跨文件访问
(a文件定义,b文件extern。a文件定义 exturn,b文件调用头文件)
可以在声明时赋上初始值
如果没有赋初始值,系统自动赋值为0
存储位置:既非堆,也非栈,而是专门的【全局(静态)存储区static】!

局部变量(自动变量)
函数内部声明
仅当函数执行时存在
仅在本文件本函数内可访问
存储位置:自动保存在函数的每次执行的【栈帧】中,并随着函数结束后自动释放,另外,函数每次执行则保存在【栈】中

static关键词
作用在:1文件作用域,2作用在函数作用域
static是全局变量时,函数不得被其他文件调用。
是局部变量时,只会被初始化一次,而且变量存在全局储存区中,而不是函数栈中。生命周期会持续程序结束。
const关键词
const是constant的简写,是不变的意思。但并不是说它修饰常量,而是说它限定一个变量为只读。修饰形参,全局变量
修饰数组
例如使用const关键字修饰数组,使其元素不允许被改变:
const int arr[] = {0,0,2,3,4}; //与int const arr[]等价
arr[2] = 1; //编译错误
修饰指针
修饰指针的情况比较多,主要有以下几种情况:
1.const 修饰 *p,指向的对象只读,指针的指向可变:
int a = 9;
int b = 10;
const int *p = &a;//p是一个指向int类型的const值,与int const p等价
p = 11; //编译错误,指向的对象是只读的,不可通过p进行改变
p = &b; //合法,改变了p的指向
这里为了便于理解,可认为const修饰的是
p,通常使用
对指针进行解引用来访问对象,因而,该对象是只读的。
2.const修饰p,指向的对象可变,指针的指向不可变:
int a = 9;
int b = 10;
int * const p = &a;//p是一个const指针
*p = 11; //合法,
p = &b; //编译错误,p是一个const指针,只读,不可变
3.指针不可改变指向,指向的内容也不可变
int a = 9;
int b = 10;
const int * const p = &a;//p既是一个const指针,同时也指向了int类型的const值
*p = 11; //编译错误,指向的对象是只读的,不可通过p进行改变
p = &b; //编译错误,p是一个const指针,只读,不可变

静态变量
函数外面 或 内部声明(即可修饰原全局变量亦可修饰原局部变量)
仅声明该变量的文件可以访问
可以在声明时赋上初始值(static关键词)
如果没有赋初始值,系统自动赋值为0
存储位置:既非堆,也非栈,而是专门的【全局(静态)存储区static】!

内存分区
堆和栈首先要清楚的是程序对内存的使用分为以下几个区:

栈区(stack):由编译器自动分配和释放,存放函数的参数值,局部变量的值等。操作方式类似于数据结构中的栈。
堆区(heap):一般由程序员分配和释放,若程序员不释放,程序结束时可能由操作系统回收。与数据结构中的堆是两码事,分配方式类似于链表。
全局区(static):全局变量和静态变量存放在此。
文字常量区:常量字符串放在此,程序结束后由系统释放。
程序代码区:存放函数体的二进制代码。

常用数据结构有哪些
数组
是可以再内存中连续存储多个元素的结构,在内存中的分配也是连续的,数组中的元素通过数组下标进行访问,数组下标从0开始。(存在栈上,先进后出)

栈是一种特殊的线性表,仅能在线性表的一端操作,栈顶允许操作,栈底不允许操作。 栈的特点是:先进后出,或者说是后进先出,从栈顶放入元素的操作叫入栈,取出元素叫出栈。

堆是一种比较特殊的数据结构,可以被看做一棵树的数组对象,具有以下的性质:堆中某个节点的值总是不大于或不小于其父节点的值;堆总是一棵完全二叉树。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。常见的堆有二叉堆、斐波那契堆等。
队列
队列与栈一样,也是一种线性表,不同的是,队列可以在一端添加元素,在另一端取出元素,也就是:先进先出。从一端放入元素的操作称为入队,取出元素为出队。
链表
链表是物理存储单元上非连续的、非顺序的存储结构,数据元素的逻辑顺序是通过链表的指针地址实现,每个元素包含两个结点,一个是存储元素的数据域 (内存空间),另一个是指向下一个结点地址的指针域。根据指针的指向,链表能形成不同的结构,例如单链表,双向链表,循环链表等。
在这里插入图片描述
volatile关键词
加了关键词修饰,则会从内存中重新装载内容,而不是直接从寄存器中拷贝内容。
例子:多线程应用中被几个任务共享的变量
当读取一个变量,为了提高速度,编译器进行优化时又是会先把变量读取到寄存器中,以后直接从寄存器上读取。如果本线程中改变变量值,会同时把变量新值放到寄存器上,而在其他线程中修在该变量,则不会改变寄存器的值。

什么是进程?什么是线程?
进程是系统中正在运行的一个程序。(运行的qq就是一个进程)
进程是系统分配资源的最小单位,每个进程都拥有独立的地址空间。

线程是进程的一个实体,是进程的一条执行路径。
一个进程可以拥有多个线程,每个线程使用其所属进程的栈空间。
同一进程内的多个线程会共享部分状态,多个线程可以读写同一块内存。同时,每个线程还拥有自己的寄存器和栈,其他线程可以读写这些栈内存。

选择:
需要频繁创建销毁的优先使用线程;进程来说创建和销毁开销很大的。
需要更稳定安全时,适合选择进程;需要速度时,选择线程更好。

*请简述进程和线程的区别(进程和线程什么关系/进程线程优缺点)
答:
根本区别:进程是操作系统资源分配的基本单位,而线程是任务调度和执行的基本单位

在开销方面:每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。

所处环境:在操作系统中能同时运行多个进程(程序);而在同一个进程(程序)中有多个线程同时执行(通过CPU调度,在每个时间片中只有一个线程执行)

内存分配方面:系统在运行的时候会为每个进程分配不同的内存空间;而对线程而言,除了CPU外,系统不会为线程分配内存(线程所使用的资源来自其所属进程的资源),线程组之间只能共享资源。

包含关系:没有开辟线程的进程可以看做是单线程的,一个进程至少有一个线程,如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的;线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程

有了进程为什么还需要线程
1.节省空间。进程开销很大,对于进程a正在执行的任务1阻塞了,这个想要使用另一个进程b去完成进程a的任务2的效果(如果需要的话并将结果传回给进程a),那么进程b就要对进程a的很大一部分资源(代码,数据)都要进行一部分拷贝,这就造成了在这些数据在内存中的重复,浪费了空间
2.节省时间,因为线程共享进程的页表和内核栈,但这两者的切换在时间上也是可以忽略不计的,真正节省时间是因为不用切换虚拟内存空间从而导致TLB不会被清空不会失效,若切换进程则TLB则会失效。

线程开发会遇到什么问题
线程不安全
先看一个例子:假设两个线程t1\t2都要对num进行操作(增1),t1和t2都各自对num修改10次,num最终的值应该为20.紧接着我们把10次改为100000000次,由于多线程访问,有可能不一样的结果。

问题分析:
问题产生的原因就是没有控制多个线程对同一资源的访问,对数据造成破坏,
使得线程运行的结果达不到预期。这种现象我们称为“线程不安全”
解决思路:
1.t1被调用的时候,获取g_num=0, 然后上一把锁,即不允许其它线程操作g_num。
2.对num进行加1
3.解锁,g_num = 1,其它的线程就可以使用g_num的值,而且g_num的值不是原来的0
4.同理其它线程在对num进行修改时,都要先上锁,处理完成后再解锁。
在上锁的整个过程中,不允许其它线程访问,保证了数据的正确性。

锁的好处:
1.确定了某段代码只能由一个线程从头到尾完整地执行。
2.全局变量的安全

锁的坏处:
1.阻止了多线程的并发执行,包含锁的某段代码实际上只能以单线程模块执行,效率大大地下降了。
2.由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁的时,可能会造成“死锁”。

3、死锁
死锁概念:在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源时,就会造成死锁。尽管死锁很少发生,但一旦发生就会造成应用的停止响应。
如何避免死锁:
解决:线程每次只锁定一个对象并且在锁定该对象的过程中不再去锁定其他的对象

死锁的两种情况:
(1)同一个线程先后两次调用lock,在第二次调用时,由于锁已经被自己占用,该线程会挂起等待自己释放锁,由于该线程已被挂起而没有机会释放锁,因此 它将一直处于挂起等待状态,变为死锁;
(2)线程A获得了锁1,线程B获得了锁2,这时线程A调用lock试图获得锁2,结果是需要挂起等待线程B释放锁2,而这时线程B也调用lock试图获得锁1,结果是需要挂起等待线程A释放锁1,于是线程A和B都在等待对方释放自己才释放,从而造成两个都永远处于挂起状态,造成死锁。

进程间通信 (IPC)
线程间可以直接读写进程数据段(如全局变量)来进程通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。

单机的IPC的方式通常有管道(PIPE)(包括无名管道和命名管道)、消息队列、信号量、共享内存
多机的IPC的方式有套接字(Socket)、Streams。Socket和Streams支持不同主机上的两个进程IPC。

管道(无名)
特点:半双工。具有固定的读端和写端。它只能用于具有亲缘关系的进程之间的通信(也是父子进程或者兄弟进程之间)。
如在A进程中关闭fd[0],那么在B进程中就关闭fd[1],这样就构成了一个单向通道。B进程写,A 进程读。/

#include<uinstd.h>
int pipe (int fd[2]);
返回值;若成功返回0,失败返回-1;
#include<stdio.h>
#include<unistd.h>
int main()
{
int fd[2];
pid_t pid;
char buf[20]={“hello world”};
if(pipe(fd)<0){
printf(“Creat pipe error”);
}

if(pid=fork()<0){//创建父子进程
printf(“Fork error”);
}else if(pid>0){//父进程关闭读,进行写入
close(fd[0]);
write(fd[1],buf,20);
}else{
close(fd[1]);
read(fd[0],buf,20);//子进程读取buf中的数据
printf(“buf=%s,pid=%d\n”,buf,getpid());
}
return 0;
}

它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write 等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。

FIFO有路径名与之相关联,它以一种特殊设备文件形式存在文件系统中。

有名/命名管道(FIFO)
Linux 系统内部是以文件节点(inode)的形式存在的
使用FIFO就可以在任意两进程之间进行通信了,当然了有一个限制就是必须要是两进程同时打开才可以。
因为命名管道是存在于文件系统中的文件节点,所以我们可以用建立文件节点的方式来建立命名管道。

原型:
#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
//函数原型:int mkfifo(const char *pathname, mode_t mode);
pathname: 普通的路径名,也就是创建后 FIFO 的名字。
mode: 文件的权限,与打开普通文件的 open() 函数中的 mode 参数相同
返回值:成功返回0,失败返回-1
创建FIFO: mkfifo("./file",0600);

在这里插入图片描述

信号
对于 Linux来说,实际信号是软中断,用来处理异步事件。许多重要的程序都需要处理信号。信号,为 Linux 提供了一种处理异步事件的方法。比如,终端用户输入了 ctrl+c 来中断程序,会通过信号机制停止一个程序。

对于sighandler_t signal(int signum, sighandler_t handler);函数来说,signum 显然是信号的编号,handler 是中断函数的指针。
int main(int argc,char *argv[])
{
int signum;
int pid;
char cmd[128]={0};
pid=atoi(argv[2]);
//atoi转换成计算机能识别的数字,字符串转换成整型数的一个函数
signum=atoi(argv[1]);
printf(“num=%d,pid=%d\n”,signum,pid);
// kill(pid,signum); //第一种方法
sprintf(cmd,“kill -%d %d”,signum,pid);//第二种方法
system(cmd);
printf(“seng signal ok\n”);
return 0;
}

信号量
信号量(semaphore)与已经介绍过的 IPC 结构不同,它是一个计数器。信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。
特点
1.信号量用于进程间同步,若要在进程间传递数据需要结合共享内存。
2.信号量基于操作系统的 PV 操作,程序对信号量的操作都是原子操作。
(原子操作:不会线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切换到另一个线程)。)
3.每次对信号量的 PV 操作不仅限于对信号量值加 1 或减 1,而且可以加减任意正整数。

什么时中断?中断发生时cpu做什么工作?
当一个经理(cpu)正处理文件时,电话铃响了(中断请求),不得不在文件上做一个记号(返回地址),暂停工作,去接电话(中断),并指示“按第二方案办”(调中断服务程序),然后,再静下心来(恢复中断前状态),接着处理文件。这就是中断。
中断优先级:.如果同时有好几个设备都要CPU来处理,CPU就会从按重要程度来逐个处理.这就是中断和中断的优先级
1 中断发生时CPU依然在工作,只是处理了中断请求而已.
2 中断发生时CPU这时正在做更重要的事情。它就会让这个中断请求先在那等着.有空时响应他.

fopen中mode参数 r, w, a, r+, w+, a+ 具体区别
r : 只能读, 可在任意位置读取
w : 只能写, 必会擦掉原有内容从头写
a : 只能写, 必不能修改原有内容, 只能在结尾追加写, 文件指针无效
r+ : 可读可写, 可在任意位置读写, 读与写共用同一个指针
w+ : 可读可写, 必会擦掉原有内容从头写
a+ : 可读可写, 必不能修改原有内容, 只能在结尾追加写, 文件指针只对读有效 (写操作会将文件指针移动到文件尾)
r+ 和 w+ 的区别:
r+ 是可以直接写在文件上,读取和写入的光标都在文件开头。
w+ 如果文件已经存在,将建立一个新文件覆盖原文件(很缺德啊……),并且支持读取。
a+ 和 r+:
a+只能在文件最后补充,光标在结尾。
r+可以覆盖前面的内容,光标在开头

strtok函数
strtok(“a1b1c1”,“1”)得到 a
用法:#include <string.h>
功能:分解字符串为一组标记串。s为要分解的字符串,delim为分隔符字符串。
说明:首次调用时,s必须指向要分解的字符串,随后调用要把s设成NULL。
strtok在s中查找包含在delim中的字符并用NULL(’\0’)来替换,直到找遍整个字符串。返回指向下一个标记串。当没有标记串时则返回空字符NULL。
举例:
#include <syslib.h>
#include <string.h>
#include <stdio.h>
main()
{
char *s=“Golden Global View”;
char *d=" “;
char *p;
clrscr();
p=strtok(s,d);
while§
{
printf(”%s\n",s);
strtok(NULL,d);
}
getchar();
return 0;
}

getchar()函数

1.从缓冲区读走一个字符,相当于清除缓冲区
2.前面的scanf()在读取输入时会在缓冲区中留下一个字符’\n’(输入完s[i]的值后按回车键所致),所以如果不在此加一个
getchar()把这个回车符取走的话,gets()就不会等待从键盘键入字符,而是会直接取走这个“无用的”回车符,从而导致读取有误

删除整个单链表

在这里插入图片描述

在这里插入图片描述

#define宏定义
写一个“标准”宏MIN,这个宏输入两个参数并返回较小的一个。
#define MIN(A,B) ((A) <= (B) ? (A) : (B))
{((A)<=(B))?}成立选第一个
不成立选第二个

逻辑运算符:a=1,b=1;
a||b-1;
因为a=1为真值,所以不管b-1是不是真值,总的表达式一定为真值,这时后面的表达式就不会再计算了。
条件运算符:a=(b>0)?b:-b;
当b>0时,a=b;当b不大于0时,a=-b;其实上面的意思就是把b的绝对值赋值给a。
位逻辑运算符
包括:1。&位与符 2。|位或符 3。^位异或符 4。~位取反符
  以操作数12为例。位运算符将数字12视为1100。位运算符将操作数视为位而不是数值。数值
  可以是任意进制的:十进制、八进制或十六进制。位运算符则将操作数转化为二进制,并相应地返回1或0。
  位运算符将数字视为二进制值,并按位进行相应运算,运算完成后再重新转换为数字。例如:
  表达式10&15表示(1010 & 1111),它将返回表示1010的值10。因为真真得真,或者是11得1,同位全是1结果也是1
  表达式10|15表示(1010 | 1111),它将返回表示1111的值15。假假得假。全零得零。
  表达式10^15表示(1010 ^ 1111), 它将返回表示0101的值5。此时是同性相斥,相同的就为假。
逻辑运算&& ||

死循环1, for(;1;){}
死循环2, while(1){}
死循环3, do {} while(1);

求交换两个变量的值,不使用第三个变量。即a=3,b=5,交换之后a=5,b=3; ?

有两种解法, 一种用算术算法, 一种用^(异或)
a = a + b;
b = a - b;
a = a - b;
or
a = a^b;// 只能对int,char… (换成二进制,同性相斥,异性相吸)
b = a^b;
a = a^b;
or
a ^= b ^= a;

c语言运算优先级
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

internet采用哪种协议,改协议主要的层次结构
tcp/ip协议族
数据链路层、网络层、传输层、应用层。
ftp在应用层

osi七层模型
物理层、数据链路层、网络层、传输层
会话层,表示层,应用层协议与硬件

socket
套接字Socket,它本身不是什么协议,而是一种支持TCP/IP协议的通信接口
创建Socket连接的时候,允许指定当前的传输层协议,当Socket连接的双方握手确认连上之后,此时采用的是TCP协议;当Socket连接的双方未确认连上就自顾自地发送数据,此时采用的是UDP协议。在TCP协议的实现过程中,每次建立Socket连接至少需要一对套接字,其中一个运行于客户端,用的是Socket类;另一个运行于服务端,用的是ServerSocket类。

服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束。
在这里插入图片描述
strcmp
返回值:

(1)字符串1=字符串2,返回0

(2)字符串1>字符串2,返回一个正整数

(3)字符串1<字符串2,返回一个负整数。

memcmp的用法
比较内存区域buf1和buf2的前count个字节。
返回值
当buf1<buf2时,返回值<0

当buf1=buf2时,返回值=0

当buf1>buf2时,返回值>0

链表
头插法代码:
Linklist Creat_list(Linklist head) {
head = (Linklist)malloc(sizeof(Lnode)); // 为头指针开辟内存空间
Lnode *node = NULL; // 定义新结点
int count = 0; // 创建结点的个数
head->next = NULL;
node = head->next; // 将最后一个结点的指针域永远保持为NULL
printf(“Input the node number: “);
scanf(”%d”, &count);
for (int i = 0; i < count; i++) {
node = (Linklist)malloc(sizeof(Lnode)); // 为新结点开辟内存空间
node->data = i; // 为新结点的数据域赋值
node->next = head->next; // 将头指针所指向的下一个结点的地址,赋给新创建结点的next
head->next = node; // 将新创建的结点的地址赋给头指针的下一个结点
}
return head;
}
尾插法代码:
Linklist Creat_list(Linklist head) {
head = (Linklist)malloc(sizeof(Lnode)); // 为头指针开辟内存空间
Linklist node = NULL; // 定义结点
Linklist end = NULL; // 定义尾结点
head->next = NULL; // 初始化头结点指向的下一个地址为 NULL
end = head; // 未创建其余结点之前,只有一个头结点
int count = 0 ; // 结点个数
printf(“Input node number: “);
scanf(”%d”, &count);
for (int i = 0; i < count; i++) {
node = (Linklist)malloc(sizeof(Lnode)); // 为新结点开辟新内存
node->data = i; // 新结点的数据域赋值
end->next = node;
end = node;
}
end->next = NULL;
}

简述TCP/IP协议三次握手和四次挥手

三次握手:
概念:指在发送数据的准备阶段,服务器和客户端之间需要三次交互
第一次握手:建立连接时,客户端向服务器发送一个SYN包,并进入SYN_SENT状态,等待服务器确认
第二次握手:当服务器收到客户端的请求后,此时要给客户端给一个确认信息ACK,同时发送SYN包,此时服务器进入 SYN_RECV状态
第三次握手:客户端收到服务器发的ACK+SYN包后,向服务器发送ACK,发送完毕之后,客户端和服务器进入 ESTABLISHED(TCP连接成功)状态,完成三次握手

四次挥手:
概念:所谓四次挥手就是说关闭TCP连接的过程,当断开一个TCP连接时,需要客户端和服务器共发送四个包确认
第一次挥手:客户端发送一个FIN,用来关闭客户端到服务器的数据传输,客户端进入FIN_WAIT_1状态
第二次挥手:服务器收到FIN后,发送一个ACK给客户端,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序 号),服务器进入CLOSE_WAIT状态
第三次挥手:服务器发送一个FIN,用来关闭服务器到客户端的数据传输,服务器进入LAST_ACK状态
第四次挥手:客户端收到FIN后,客户端进入TIME_WAIT状态,接着发送一个AKC给服务器,确认序号为收到序号+1,服务器进入CLOSED状态,完成四次挥手

常见的问题:
1、为什么连接需要三次,关闭的时候需要四次?
当服务器收到客户端的连接请求后,可以直接发送SYN+ACK报文,其中ACK是确认应答,SYN报文是用来同步的。但 是在关闭连接的时候,当服务器收到FIN的时候,很可能并不会立刻关闭SOCKET,所以只能先回个ACK来应答,只有等服务器把所有报文都发完了才能发送FIN,因此不能一起发送,所有需要四步。
2、为什么断开链接的时候客户端设置的定时器时间等待要2MSL(两个通信报文的最大时间)?
当客户端最终告诉服务器断开确认的时候,他不知道自己的发出的指令是否准确的一次性被服务器接收。
3、为什么TIME_WAIT状态需要经过2MSL(最大报文段生存时间)才能返回到CLOSE状态?
虽然按道理,四个报文都发送完毕,我们可以直接进入CLOSE状态了,但是我们必须假象网络是不可靠的,有可以最后一个ACK丢失。所以TIME_WAIT状态就是用来重发可能丢失的ACK报文

内存分配问题

内存所在的位置

调用malloc吗,由程序员分配
一直存在,直到free,对应的指针依然指向该内存。

调用函数,由系统分配的,一般用于存放局部变量,函数参数,等等
函数调用结束,栈里的内容就被回收。

void GetMemory(char **p, int num)
{
*p = (char *)malloc(num);// 堆 }
void Test1()
{
char *str = NULL;
GetMemory(&str, 100);
strcpy(str, “xmgcc”);
printf("%s\n", str);
}
正确,输出内容:xmgcc
GetMemory申请的内存在堆上
通过函数参数获取分配的内存,只能通过二级指针拿到

void Test11()
{
char str = NULL;
GetMemory(str, 100);
strcpy(str, “xmgcc”);
printf("%s\n", str);
}
程序存在问题,GetMemory申请的内存在堆上
通过函数参数获取分配的内存,只能通过二级指针拿到
//
char
GetMemory2()
{
char p[] = “xmgcc”;// 栈
return p;
}
void Test2()
{
char *str = NULL;
str = GetMemory2();
printf("%s\n", str);
}
程序存在问题,GetMemory2申请的内存在栈上,GetMemory4返回后p所在的内存被系统回收。
,打印的值可能是任意值
//
char *GetMemory3()
{
char *p = malloc(100); // 堆
return p;
}
void Test3()
{
char str = NULL;
str = GetMemory3();
strcpy(str, “xmgcc”);
printf("%s\n", str);
}
正确,输出内容:xmgcc
GetMemory3申请的内存在堆上
通过变量保存函数返回申请内存的首地址
//
char
GetMemory4()
{
char p[100];// 栈
return p;
}
void Test4()
{
char *str = NULL;
str = GetMemory4();
strcpy(str, “xmgcc”);
printf("%s\n", str);
}
错误,GetMemory4申请的内存在栈上,GetMemory4返回后p所在的内存被系统回收。
因此str变成野指针,strcpy可能崩溃
//
void Test5()
{
char *str = (char *)malloc(100); // 堆
strcpy(str, “xmgcc”);
free(str); // 释放内存,str依然指向申请的内存,也就是str的值不是NULL
if (str != NULL) {
strcpy(str, “welcome”);// 这句话会被执行
}
printf("%s\n", str);
}
程序存在问题,free后, str指向的内存被释放,但是str依然指向申请的内存,strcpy(str, “welcome”)会被执行
但是这么写会导致bug。
内存被释放后,不能再对内存进行操作。需要将指针置位NULL;
str = NULL;
void GetMemory6(char *p)
{
p = (char *)malloc(100);// 堆
strcpy(p, “xmgcc”);
}
void Test6()
{
char *str = NULL;
GetMemory6(str);
printf("%s\n", str);
free(str);
}
输出内容:(null), GetMemory6函数申请的内存在堆上
通过函数参数获取分配的内存,只能通过二级指针拿到

数组基础知识
特性
每个数组元素的类型是一样的
数组名指向数组的首地址,是常量
内存是连续的,
支持随机访问,a[0], a[2]
指针和数组
指针+1移动的大小,是sizeof(类型)
short a[4];
short *p = a;
printf("%p\n", p + 0);
printf("%p\n", p + 1);
printf("%p\n", p + 2);
printf("%p\n", p + 3);
每个地址间隔2,也就是sizeof(short)的大小。
指针+1,移动sizeof(short)的大小,也就是指针所指向的类型的大小

结构体大小计算专题
结构体
对齐 : 自身类型的整数倍,int ,4的整数倍
填充
不填充

计算大小的规律
原则一:结构体中元素是按照定义顺序一个一个放到内存中去的,但并不是紧密排列的。从结构体存储的首地址开始,每一个元素放置到内存中时,它都会认为内存是以它自己的大小来划分的,因此元素放置的位置一定会在自己宽度的整数倍上开始(以结构体变量首地址为0计算)。
原则二:在经过第一原则分析后,检查计算出的存储单元 是否为所有元素中最宽的元素的长度的整数
倍,是,则结束;若不是,则补齐为它的整数倍。

在这里插入图片描述

填充的字节用[]表示
最宽字节8
1 [3] 4 8
struct X {
char a; //1
int b; //4
double c; //8
} S1;
最宽字节8
1 [7] 8 4 [4]
struct X {
char a;
double b;// 放在8的整数倍上
int c;
} S2;
最宽字节8
8 1 [3] 4
struct X {
double a;
char b;
int c;
} S3;
最宽字节8
8 1 [3] 4 1 [3] 4
struct X {
double a;
char b;
int c;
char d;
int e;
} S5;
最宽字节8
4 [4] 8 1 [3] 4 1 [7]
struct X {
int e;
double a;
char b;
int c;
char d;
} S6;
最宽字节8
1 [3] 4 8
struct X {
char a;
int b;
double c;
};
结构体嵌套
最宽字节8,struct X里面的最宽元素是8,struct Y的是1
struct X最宽元素是8字节,所有需要按8的整数倍来存放struct X
1 [7] 16
struct Y {
char a;
struct X b;
};
// __attribute((packed)),编译器选项,不填充
4 1
struct tag0 {
int a;
char b;
} __attribute((packed));
4 4
struct tag1 {
int a;
int b;
} __attribute((packed));
// 64位
4 4 8
struct tag2 {
int a;
int b;
char *c;// 指针,64位下占8个字节
} __attribute((packed));
4 4
struct tag3 {
int a;
int b;
char c[0]; // 占位符,大小为0,
} __attribute((packed));
4 4 1
struct tag4 {
int a;
int b;
char c[1]; // 字符数组,只有一个元素,占用sizeof(char),1个字节
} __attribute((packed));
// 省空间,较少内存占用
5 [3] 5 [3] 5 [3] 5 [3] 5 [3],总共5个字节
typedef struct AA {
unsigned char b1 : 5; // 只使用前5位,后3位不用
unsigned char b2 : 5;
unsigned char b3 : 5;
unsigned char b4 : 5;
unsigned char b5 : 5;
} AA;
5 5 5 5 5,占了4个字节
typedef struct BB {
unsigned int b1 : 5;
unsigned int b2 : 5;
unsigned int b3 : 5;
unsigned int b4 : 5;
unsigned int b5 : 5;
} BB;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值