2025c语言作业复盘
2025年c语言作业复盘
实验一
sy1-2

- 短整型占2个字节(有符号) 取值范围-32768~32767
- 无符号短整型占两个字节,取值0~65535
c=-1因为c为无符号短整型 而-1的补码全为1,赋值无符号短整型时即 1111 1111 1111 1111 (65535)b=db为短整型 (长整型d赋值给b发生截断,即截断d的后16位给b)hd(短整型) hu(无符号短整型) hx(短整型十六进制)
sy1-3

- 注意求值的顺序会根据编译系统的不同而不同
1 | printf("i=%d,j=%d,i=%d,j=%d",i,j,i++,j++) |
- “短路现象” 不是所有表达式都被运算!!!
对于或‘a|b‘ 只要a为真则表达式b就不用算
sy1-4

可用常变量来替代宏定义(const )
sy1-5

用条件运算符来判断字母是否产生回绕
1 | c>'z' || c>'Z' && c<'a' ? c-=26:c; |
实验二
sy2-3

在switch选择中不需要加12月份 直接加day即可只需要在二月做好区分即可这里不用加break跳出选择,只用找到匹配的月份后往下执行相加即可(这也是为什么选择倒序的原因)
sy2-4

- 注意在条件表达式中注意区分等于
==与赋值=算术符
实验三
sy3-3

- 用循环语句输出倒序金字塔
1 | int i,j,n; |
- 如果是打入正序金字塔
1 | int i,j,n; |
sy3-4

1.利用t原来循环的值继续除以i这样就不用特地的求每一次的阶乘
2.精度 当加上的某一项小于精度时,即退出循环
实验四
sy4-3

插入数据
1 | for(k=0;k<n;k++) //这里用一个新的变量来记录应该插入的位置 |
选择排序法
1 | int i,j,k; |
实验五
sy5-3

- 删除尾部的某一种符号 可以利用循环从右到左找到第一个不是这种符号的字符 然后记住下标 并且把此下标的下一个字符赋值为‘\0’这样就不用一直想着说如何删除
sy5-4

- 如何把小写字母转换为大写
- 如果是小写 (x-‘a’(找到偏移量)+‘A’) (对应的可以找到此偏移量在大写中应该为什么字母)
实验六
sy6-1
- 值传递(相当于形参为实参的简单copy件)
- 在调用函数时如果函数自成语句像这样
eg.swap(x,y);函数swap交换x,y的值,在函数调用时形参x,y的值已经交换成功,但当函数结束时,形参被分配的内存空间被释放,可以认为函数所改变的值没来得及传递给主函数)
- 在调用函数时如果函数自成语句像这样

这个就是很典型的值传递,但函数调用结束完后直接清除空间,我觉得可以相当于没有调用函数(或者说调用函数没有用)
- 但如果是在赋值语句中,即表达式中,虽然实参x,y的值也没有被改变,但是其函数运行的结果有效的传递到主函数中 (
eg. sum=max(x,y);取最值函数的结果有效赋值给sum,虽然函数最后也是被释放,但与简单的调用不同)

如上述代码 虽然只是简单的值传递,但是函数运行的结果有被传送到主函数中 (但实参x,y的值并没有改变)
地址传递(传递地址,但要看你是改变指针变量的值还是改变指针变量所指向的值的值)
- 改变指针变量的值(如果在交换函数中,一般是把中间变量
int *t与指针变量直接交换)—其实就为简单的值传递
1
2
3
4
5
6
7int swap(int *x,int *y)
{
int *t; //与指针变量交换的话 中间变量应为与指针变量同一个类型,即指针类型
t=x;
x=y;
y=t;
}- 改变指针变量所指向的值的值(在交换函数中,把中间变量与指针变量所指向的值进行交换)—这种才能有效的改变实参的值
1
2
3
4
5
6
7int swap(int *x,int *y)
{
int t; //这里的中间变量为了与指针变量所指向的值进行交换 ,所以t为整型
t=*x;
*x=*y;
*y=t;
}- 改变指针变量的值(如果在交换函数中,一般是把中间变量

sy6-3
使用半折法寻找数(前提已经排好序的一组数)
—ps:下面方法是在一组升序排法的数中

1 | int search(int a[],int n,int x) |
sy6-4**(有关取模运算)**

其实有关于加密代码的程序
1 | s[i]=(s[i]+d-'A'+26)%26+'A'; //前提是已经确定字母是大写还是小写 然后在各自的表达式中运算 |
sy6-5(理解变量的定义域)

- 全局变量和静态变量如果未赋初始值,那么默认为0
实验七
sy7-3(用链表来操作学生信息)
- 定义结构体变量
1 | //首先需要用结构体来表示一个结点 每一个结构体的尾部放有指向下一个结点的指针 |

- 使用头插法创建带有头结点的链表,并且返回头指针
- 注意后续调用此函数应为这种
head=create();把头指针h赋值给指针变量head
- 注意后续调用此函数应为这种
1 | struct student * create() //创建一个返回结构体类型的指针 |
- 插入新的数据
1 | void insert(struct student *h,struct student *x) |
- 删除数据(删除时只有进行学号比较即可,故引入的新数据为一个数组就好)
1 | void dele(struct student *h,char x[]) //想要删除需要先查找到要删除的结点,故前面与查找函数类似 |
sy7-4(结构体,联合体,共用体的区分)

1 | b.ch[0]='a'; |
实验八
sy8-1(用文件来输入输出)
- 第一步 定义文件指针
1 | FILE *fp_in,*fp_out; |
- 以写的方式打开输入文件 (如果原来没有该文件则重新创建)
1 | if((fp_out=fopen("d:\\data\\sy8-1-out.txt","w"))==NULL) //fopen函数的两个参数都为字符串,两个部分:即文件在哪 和以那种方式打开文件 (fopen函数是为该文件分配一个缓冲区,并且返回该缓冲区的首地址 所以要把fopen函数的返回值赋值给文件指针) |
- 输入字符到该文件中 (
fputc 函数 一次写入一个字符到该文件中)
1 | while((ch=getchar())!='#') //字符ch由getchar函数输入(输入到屏幕上) 如果不为字符'#'则用fputc函数输入到文件中 |
- 用只读的形式打开前面写入的文件**(为了以防文件没有被正确的输入)**
1 | if((fp_in=fopen("d:\\data\\sy8-1-out.txt","r"))==NULL) |
- 以只写的形式打开输出文件(如果原来没有输出文件就重新创建)
1 | if((fp_out=fopen("d:\\data\\sy8-1-out1.txt","w"))==NULL) |
- 在该文件中把小写字母转换为大写字母(以循环)
1 | ch=fgetc(fp_in); //先读取该文件的第一个字符 |
sy8-2

- 从输入文件中读取数据(fscanf函数)
1 | int n=0; |
然后关闭文件在主函数中排序
打开输出文件

1 | //通过循环有效去除最小数和最大数 |
C语言常见安全漏洞与修复意见
- 栈缓冲区溢出
strcpy(buffer input)strcpy不检查目标缓冲区大小 导致栈溢出- 应该用
strncpysnprintf
- 格式化字符串漏洞
printf(user_input);//危险由用户控制字符串大小- 因为若用户所输入的包含格式字符如
%n(写入内存) %x(泄露信息)那么printf函数会把它当成格式字符来处理 - 应使用
printf("%s",user_input)
- 堆溢出(用户自己分配内存的变量)
strcpy(buffer,data)//buffer所分配的内存过小 data字符串的长度过长- 应该检查数据长度,使用安全复制函数
- 整数溢出
char *buffer = (char *)malloc(size+1)//由用户来输入size的话如果此时为最大整数的话 就会产生溢出变为负数或者很小的值- 应该检查加法是否溢出
if(size >INT_MAX -1)
- 释放后使用(Use-After-Free)
free(ptr); printf("%s\n",ptr);//危险访问已经释放内存的变量- 可能会导致内存泄漏 或者执行任意的代码
- 应释放后立即置为NULL:
free(ptr); ptr=NULL;
- 双重释放(Double Free)
free(buffer); free(buffer);//重复释放同一块内存- 破坏堆管理结构,可能导致任意代码执行
- 释放后应把指针置为NULL,这样第二次free就会失败
- 未初始化内存就使用
int *ptr=(int*)malloc(sizeof(int) *10); 然后就直接if(ptr[0]==0)//因为未赋初始值所以值并不确定- 应使用
calloc或者直接手动初始化内存
- 竞态条件(TOCTOU)
sleep(1)在模拟完耗时操作才打开文件- 在检查和使用文件之间存在时间窗口
- 攻击者可在此期间替换文件
- 应该使用原子操作或者文件描述符
- 命令注入
snprintf(command,sizeof(command)," ",username); system(command);//用户的输入直接拼接到命令中 那么可能就会有恶意代码输入- 应使用白名单验证,避免使用
system()
- 数组越界访问
- 没有检查输入小标的大小是否在数组的边界中
- 可能会读取/写入相邻的内存,导致信息泄露或者内存损坏
- 应检查索引范围
if(index>=0 && index<=5)
- 越界读操作
- 由于数组的下标未进行检查 可能读取到相邻内存
- 就会导致泄露相邻内存中的敏感数据
- 应该严格的验证索引范围
- 不安全的内存拷贝
memcpy(buffer,input,copy_size)内存拷贝有问题memcpy中的size_t(是无符号数) 故如果输入一个负数如-1 那么转化为无符号数的话会为极大值- 那么这样就会导致缓冲区溢出
- 应在转换为无符号前检查size是否为负数
- 错误的边界检查逻辑
- 就是判断数组的下标时判断错误
- 如
c[256] 认为数组下标到达256 - 应该检查输入的长度是否小于缓冲区大小
- 未检查的动态内存分配结果
- 即动态分配内存
malloc但却没有检查内存分配是否分配成功 然后就直接使用该内存 - 应该检查malloc的返回值是否未NULL
- 即动态分配内存
- 字符串连接溢出
- 就是字符串连接时未检查被连接的数组的内存大小是否够长
strcat会直接连接字符串,不检查目标缓冲区剩余的空间- 使用
strncat函数会检查边界
- 有符号整数作为数组索引
- 输入数组下标时不能仅仅关注其是不是小于数组最大值的下标,还要关注他是不是下标>=0
- 如果只关注其中一个就可能造成负数作为数组下标 这样就会访问错误
- 应该检查索引是否大于等于0且小于MAX_SIZE
- 不安全的指针运算
- 定义一个指针
char *ptr=buffer如果此时进行运算ptr=ptr+offsetoffset是任意输入的数字 那么就会产生使指针的偏移量超出数字的范围 - 这样就会破话其他内存区域
- 应该验证偏移后的指针是否是在有效范围内
- 定义一个指针
- 不安全的文件读取
- 打开文件时是使用文件的相对路径 那么可能就会打开意外的文件 应该使用绝对路径
- 用
fread读取文件可能会读取超过缓冲区范围的数据 - 应该验证路径,并且限制读取的大小
- 格式字符串写入漏洞
- 格式字符串包含用户输入,这样攻击者就可以在字符串中任意写入格式字符,向任意地址写入数据
- 修复:不使用用户的输入作为格式字符串
- 多线程竞态过程
- 多线程可能同时通过
- 应该使用互斥锁保护临界区
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 My blog!