【C语言学习笔记】:字符串处理、动态数据、数组与结构体

目录

一、字符串处理函数常见漏洞及防御措施

二、动态数据实现

三、结构体

四、练习


一、字符串处理函数常见漏洞及防御措施

(一)常见漏洞

1.1 缓冲区溢出

调试的诡异崩溃现场:

char buffer[10];
strcpy(buffer, "这个字符串绝对不止10个字节!"); // 直接触发内存雪崩

strcpy函数会一直复制源字符串,直到遇到字符串结束符'\0'为止,而不会去判断目标数组是否足够大来容纳源字符串。如果源字符串长度大于目标数组长度,就会导致溢出。

1.2 格式化字符串漏洞

某次代码审计发现的典型问题:

char name[20] = "%s%sBingo";
printf(name);     // 若name中包含格式化字符串如"%s%s",可能会泄露栈信息

       当printf函数的格式字符串参数是由用户输入控制且未正确转义时,攻击者可以构造特殊的格式化字符串,如%x%s等,来读取或写入栈中的数据,从而获取敏感信息或改变程序执行流程。

(二)防御措施

1. 使用安全函数

       用strncpystrncat等带有长度限制的函数替代strcpystrcat,它们可以指定最多复制或连接的字符数量,有效防止缓冲区溢出。

char dest[10];
char src[15] = "Hello, World!";
strncpy(dest, src, sizeof(dest) - 1); // 复制时限制长度,避免溢出
dest[sizeof(dest) - 1] = '\0'; // 确保字符串以'\0'结尾

   strncpy函数在复制字符串时,最多复制指定的长度,这样可以确保不会超出目标缓冲区的范围,避免了缓冲区溢出问题。

2. 严格检查输入

       对所有来自外部的输入进行严格长度检查和合法性验证,确保输入数据在合理范围内,避免因异常输入触发漏洞。

char input[100];
printf("请输入您的名字(最多99个字符):");
scanf("%99s", input); // 限制输入的最大长度,防止缓冲区溢出

3. 避免危险用法

       在使用printf等函数时,确保格式字符串是固定的,不直接使用用户输入作为格式字符串,或者对用户输入的格式字符串进行严格转义和限制。

char name[20] = "Bingo";
printf("%s", name); // 安全用法,固定格式字符串

二、动态数据实现

         在C语言中,动态数据的实现主要依赖于动态内存分配函数,如malloccallocreallocfree。这些函数允许程序在运行时根据需要分配和释放内存,从而灵活地处理数据。

malloc函数

  • 功能:分配指定大小的内存块,返回指向该内存块的指针。

  • 函数原型void *malloc(size_t size);

使用示例

#include <stdio.h>
#include <stdlib.h>

int main() {
    int *arr;
    int n = 5;

    // 分配存储5个整数的内存
    arr = (int*)malloc(n * sizeof(int));
    if (arr == NULL) {
        fprintf(stderr, "内存分配失败!\n");
        return 1;
    }

    // 使用分配的内存
    for (int i = 0; i < n; i++) {
        arr[i] = i * i;
        printf("%d ", arr[i]);
    }
    printf("\n");

    // !!!释放内存
    free(arr);
    arr = NULL; // !!!避免悬空指针,因为可能还存有地址
    return 0;
}

malloc函数的注意事项

在调用malloc后,必须检查其返回值是否为NULL,以确保内存分配成功。如果返回NULL,表示内存分配失败,此时应采取相应的错误处理措施。动态分配的内存必须手动释放,否则会导致内存泄漏。可以使用free函数释放由malloc分配的内存。确保每次malloc都有对应的free。一旦内存被释放,应避免继续使用已释放的内存,这可能导致未定义行为。可以将指针设置为NULL以防止误用。

三、结构体

(一)基本语法

// 基本语法
struct 结构体名 {
    数据类型 成员1;
    数据类型 成员2;
    ...
};

// 示例
typedef struct
{
    char name[128]; // 姓名
    char sex[5];    // 性别
    short id;       // 学号
    int age;        // 年龄
    int sco;        // 成绩
} stu_data;

(二)结构体的使用

声明结构体变量

struct Student s1;

访问结构体成员

strcpy(s1.name, "Bingo");
s1.age = 18;
s1.sco = 66;

初始化结构体

struct Student s2 = {"Bingo", 18, 66};   //我感觉最简便的

(三)结构体与指针

结构体指针

可以定义指向结构体的指针,通过指针访问结构体成员时,可以使用->运算符。

struct Student *p = &s1;
printf("%s", p->name); // 输出"Bingo"

动态分配结构体内存

使用malloc函数为结构体分配内存。

struct Student *s3 = (struct Student *)malloc(sizeof(struct Student));
if (s3 != NULL) {
    strcpy(s3->name, "Bingo");
    s3->age = 18;
    s3->score = 66;
    free(s3); // 使用完毕后释放内存
}

(四)嵌套结构体

结构体可以包含其他结构体作为成员,这称为嵌套结构体。

struct Address {
    char street[100];
    char city[50];
    char state[50];
};

struct Person {
    char name[50];
    int age;
    struct Address addr; // 嵌套的结构体
};

struct Person p1 = {"小彬", 18, {"莲花路", "株洲", "湖南"}};

// 访问嵌套结构体的成员时,可以使用.运算符链式访问
printf("%s", p1.addr.city); // 输出"株洲"

(五)结构体数组

可以声明结构体数组,用于存储多个具有相同结构的数据。

struct Student students[3] = {
    {"Bingo", 18, 66},
    {"彬彬", 18, 66},
    {"小彬", 18, 66}
};

for (int i = 0; i < 3; i++) {
    printf("学生%d: %s, %d岁, 成绩:%.1f\n", i+1, students[i].name, students[i].age, students[i].sco);
}

四、练习


学生管理系统

结构体定义

  • stu_data:表示单个学生的信息,包括姓名、性别、学号、年龄和成绩。

  • stu_class:表示一个班级,包含一个最多20个学生的数组和一个记录当前学生人数的变量。

功能函数

  • tail_ins_list:在班级的尾部插入一个新学生。检查班级是否已满,并确保学号唯一。

  • tail_del_list:删除班级最后一个学生。

  • id_del_list:根据学号删除对应的学生,通过移动数组元素实现。

  • change_list:根据学号查找学生并修改其信息,可修改姓名、性别、学号、年龄或成绩。

  • find_list:根据学号查找学生,返回其在数组中的索引。

  • binary_find:实现了二分查找,但未在主函数中调用。

  • sort_list:用的简单冒泡排序,还可以用其他更快的比如快排等。

  • printf_Sql_List:打印班级中所有学生的信息。

完整代码

#include <stdio.h>

#define MAX_stu 20

// 单个学生
typedef struct
{
    char name[128]; // 姓名
    char sex[5];    // 性别
    short id;       // 学号
    int age;        // 年龄
    int sco;        // 成绩
} stu_data;

// 一个班
typedef struct
{
    stu_data data[MAX_stu]; // 学生
    int len;                // 记录学生人数
} stu_class;

/************************* 增 *************************/
/*
对 class 结构体中进行尾部数据的插入
函数 void pos_ins_list( stu_class * class , int pos);
参数
    @ class     要操作的班级
返回值
    无返回值
*/

/************************* 增 *************************/
void tail_ins_list(stu_class *class);

/************************* 删 *************************/
void tail_del_list(stu_class *class);
void id_del_list(stu_class *class);

/************************* 改 *************************/
void change_list(stu_class *class);

/************************* 查 *************************/
int find_list(stu_class *class);

/************************* 排序 *************************/
void sort_list(stu_class *class);

/************************* 打印 *************************/
void printf_Sql_List(stu_class *class);

// 如果你现在没听懂 一定是老师的问题 不是你的问题
// 我不喜欢 pua 自己的人

int main(int argc, char const *argv[])
{
    /* 初始化结构体 */
    // 创建一个 班级结构体
    stu_class stu = {
        // 学生数据初始化
        .data = {
            {"小明", "男", 1, 18, 80},
            {"小红", "女", 2, 18, 81},
            {"彬彬", "男", 3, 18, 66},
            {"小绿", "男", 4, 18, 83},
            {"小紫", "女", 5, 18, 84},
        },
        // 学生个数变量初始化
        .len = 5};

    printf_Sql_List(&stu);

    // 插入数据
    // tail_ins_list(&stu);
    // printf_Sql_List(&stu);

    // 删尾
    // tail_del_list(&stu);
    // printf("已删除:\n");
    // printf_Sql_List(&stu);

    // 删指定学号
    // id_del_list(&stu);
    // printf("已删除:\n");
    // printf_Sql_List(&stu);

    // 修改学生信息
    change_list(&stu);
    printf_Sql_List(&stu);

    return 0;
}

/************************* 增 *************************/
void tail_ins_list(stu_class *class)
{
    // 判定是否为空指针
    if (NULL == class)
    {
        puts("你没有传入班级");
        return;
    }

    // 判断满
    if (MAX_stu < class->len)
    {
        puts("满了 塞钱都没用(ps:除非给系统的开发者(小声bb))"); // 嘿嘿~
        return;
    }

    // 定义标志位 判断插入学生学号是否重复
    int flag = 0;

    // 输入数据
    stu_data data;
    printf("请输入学生数据:\n");

    while (1)
    {
        printf("姓名:");
        scanf("%s", data.name);
        printf("性别:");
        scanf("%s", data.sex);
        printf("学号:");
        scanf("%hd", &data.id);
        printf("年龄:");
        scanf("%d", &data.age);
        printf("成绩:");
        scanf("%d", &data.sco);

        for (int i = 0; i < class->len; i++)
        {
            if (data.id == class->data->id)
            {
                flag = 1;
                break;
            }
        }
        if (1 == flag)
        {
            flag = 0;
            printf("已有重复学号,请重新输入:\n");
            continue;
        }
        else
        {
            // 插入数据
            class->data[class->len] = data;
            // 迭代长度
            class->len++;
            break;
        }
    }
}

/************************* 删 *************************/
// 直接删除尾部学生
void tail_del_list(stu_class *class)
{
    // 判定是否为空指针
    if (NULL == class)
    {
        puts("你没有传入班级");
        return;
    }

    // 判断班级人数是否以为空
    if (0 >= class->len)
    {
        puts("没有人了,再删就负数啦\n");
        return;
    }
    class->len--;
}

// 删除指定学号学生
void id_del_list(stu_class *class)
{
    // 判定是否为空指针
    if (NULL == class)
    {
        puts("你没有传入班级\n");
        return;
    }

    // 判断班级人数是否以为空
    if (0 >= class->len)
    {
        puts("没有人了,再删就负数啦\n");
        return;
    }

    int del_id;
    int flag = 0;

    while (1)
    {
        printf("请输入你要删除学生的id:");
        scanf("%d", &del_id);

        for (int i = 0; i < class->len; i++)
        {
            if (del_id == class->data[i].id)
            {
                for (int j = i; j < class->len - 1; j++)
                {
                    class->data[j] = class->data[j + 1];
                }
                class->len--;
                flag = 1;
                break;
            }
        }
        if (0 == flag)
        {
            printf("没有该学号的学生\n");
            printf_Sql_List(class);
        }
        else
        {
            break;
        }
    }
}

/************************* 改 *************************/
void change_list(stu_class *class)
{
    // 判定是否为空指针
    if (NULL == class)
    {
        puts("你没有传入班级\n");
        return;
    }

    int index = 0; // 查找到的下标
    int op = 0;    // 选项

    // 查
    index = find_list(class);
    if (-1 == index)
    {
        printf("未找到数据\n");
        return;
    }

    // 修改
    printf("请输入你需要修改的项目:\n");
    printf("1、姓名\n");
    printf("2、性别\n");
    printf("3、学号\n");
    printf("4、年龄\n");
    printf("5、成绩\n");

    scanf("%d", &op);

    switch (op)
    {

    case 1:
        printf("姓名:");
        scanf("%s", class->data[index].name);
        break;
    case 2:
        printf("性别:");
        scanf("%s", class->data[index].sex);
        break;
    case 3:
        printf("学号:");
        scanf("%hd", &class->data[index].id);
        break;
    case 4:
        printf("年龄:");
        scanf("%d", &class->data[index].age);
        break;
    case 5:
        printf("成绩:");
        scanf("%d", &class->data[index].sco);
        break;

    default:
        printf("输入错误\n");
        break;
    }
}

/************************* 查 *************************/
int find_list(stu_class *class)
{
    // 判定是否为空指针
    if (NULL == class)
    {
        puts("你没有传入班级\n");
        return -1;
    }

    printf("请输入你需要查找的id号:");

    int id = 0;
    scanf("%d", &id);

    for (size_t i = 0; i < class->len; i++)
    {
        if (id == class->data[i].id)
        {
            return i;
        }
    }
    return -1;
}
// 二分查找
int binary_find(stu_class *class)
{
    // 判定是否为空指针
    if (NULL == class)
    {
        puts("你没有传入班级\n");
        return -1;
    }

    int id = 0;
    printf("请输入你需要查找的id号:");
    scanf("%d", &id);

    // 二分查找
    int left = 0;
    int right = class->len - 1;
    int mid = 0;
    while (left <= right)
    {
        mid = (left + right) / 2;
        if (id == class->data[mid].id)
        {
            return mid;
        }
        else if (id < class->data[mid].id)
        {
            right = mid - 1;
        }
        else
        {
            left = mid + 1;
        }
    }
}

/************************* 排序 *************************/
// 按成绩降序排序
void sort_list(stu_class *class)
{
    if (NULL == class)
    {
        puts("你没有传入班级\n");
        return;
    }

    for (int i = 0; i < class->len - 1; i++)
    {
        for (int j = 0; j < class->len - 1 - i; j++)
        {
            if (class->data[j].sco < class->data[j + 1].sco)
            {
                stu_data temp = class->data[j];
                class->data[j] = class->data[j + 1];
                class->data[j + 1] = temp;
            }
        }
    }
}

/************************* 打印 *************************/
void printf_Sql_List(stu_class *class)
{
    // 循环
    for (size_t i = 0; i < class->len; i++)
    {
        // 打印数据
        printf("name = %s sex = %s id = %d sco = %d age = %d \n",
               class->data[i].name, class->data[i].sex, class->data[i].id, class->data[i].age, class->data[i].sco);
    }
}

       在学习C语言的道路上,如何将基础的语法知识转化为实际的编程能力是一个关键的挑战。今天,我要和大家分享一个实战项目——用C语言实现的学生信息管理系统。这个系统不仅巩固了我对结构体、数组以及函数的理解,还让我在实践中掌握了数据的组织、存储和操作技巧。

猜你喜欢

转载自blog.csdn.net/qq_64634610/article/details/146212876
今日推荐