1. 详述函数编写思路和流程

本次实现的sprintf函数需要实现以下功能:

a) 不会产生缓冲区溢出

b) 不能被格式字符串控制输出内容

c) 实现安全的变参

为实现以上功能:
函数首先检查传入的目标缓冲区指针和大小是否有效,若无效则返回 -1 表示参数无效。

使用 va_list 类型的变量 args 来处理可变数量的参数,通过 va_start 宏初始化。

使用一个循环遍历格式化字符串 format,逐个处理其中的字符:

对于普通字符,直接将其复制到目标缓冲区中;对于 %s 格式符,从参数中获取字符串,计算其长度,然后将其复制到目标缓冲区中;对于 %d 格式符,从参数中获取整数,将其转换为字符串,然后复制到目标缓冲区中。

在复制字符串时,会检查缓冲区是否足够大,若不足则返回 -1。

循环结束后,添加字符串终止符 \0 到目标缓冲区的末尾。

最后,释放可变参数列表并返回写入目标缓冲区的字符数量(不包括终止符)。如果参数不匹配或缓冲区不足,也返回 -1。

  1. 函数源代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    #include <stdio.h>
    #include <stdarg.h>
    #include <string.h>


    int safe_sprintf(char* buffer, size_t sizeOfBuffer, const char* format, ...) {
    if (buffer == NULL || sizeOfBuffer == 0) {
    // 参数无效
    return -1;
    }

    va_list args;
    va_start(args, format);
    size_t i = 0; // 记录写入字符的位置

    while (*format != '\0') {
    // 处理格式化字符串中的普通字符
    if (*format != '%' && i < sizeOfBuffer - 1) {
    buffer[i++] = *format;
    }
    else if (*format == '%' && *(format + 1) == 's')
    {

    // 处理 %s 格式符
    const char* str = va_arg(args, const char*);
    size_t len = 0;
    // 计算字符串长度
    if (str == "-1") {
    //参数不匹配
    va_end(args);
    return -1;
    }
    while (str[len] != '\0') {
    len++;
    }
    if (i + len < sizeOfBuffer) {
    // 复制字符串到缓冲区
    size_t j;
    for (j = 0; j < len; j++) {
    buffer[i + j] = str[j];
    }
    i += len;
    }
    else {
    // 缓冲区不足
    va_end(args);
    return -1;
    }


    // 判断是否能容纳整个字符串


    // 移动 format 指针到 %s 后面
    format += 1;
    }
    else if (*format == '%' && *(format + 1) == 'd')
    {
    // 处理 %d 格式符
    long int num = va_arg(args, long int);

    // 分配足够的内存来存储整数的字符串表示
    char str[200]; // 调整大小以适应你的需要
    sprintf_s(str, sizeof(str), " %d", num);

    // 检查参数是否匹配
    if (str=="-1") {
    // 参数不匹配
    va_end(args);
    return -1;
    }

    // 计算字符串长度
    size_t len = strlen(str);

    // 判断是否能容纳整个字符串
    if (i + len < sizeOfBuffer) {
    // 复制字符串到缓冲区
    size_t j;
    for (j = 0; j < len; j++) {
    buffer[i + j] = str[j];
    }
    i += len;
    }
    else {
    // 缓冲区不足
    va_end(args);
    return -1;
    }

    format += 1;
    }

    else {
    // 未知的格式化符号
    va_end(args);
    return -1;
    }

    ++format;
    }

    // 在字符串末尾添加终止符
    if (i < sizeOfBuffer) {
    buffer[i] = '\0';
    }
    else {
    // 缓冲区不足
    va_end(args);
    return -1;
    }

    va_end(args);

    return i; // 返回写入字符的数量(不包括终止符)
    }

    int main() {
    char buffer[20];
    printf("Normal output example:\n");
    int result = safe_sprintf(buffer, sizeof(buffer), "Hello, %s%d!", "world", 1111, "-1"); //正常输出

    if (result >= 0) {
    printf("Formatted string: %s\n", buffer);
    }
    else {
    printf("Error occurred: %d\n", result);
    }

    printf("Example of Scalable Buffer:\n");
    result = safe_sprintf(buffer, sizeof(buffer), "Helloooooooooooooooooooooooooooooooooooooooo, %s%d!", "world", 11111, "-1");
    if (result >= 0) {
    printf("Formatted string: %s\n", buffer);
    }
    else {
    printf("Error occurred: %d\n", result);
    }

    printf("Example of viewing stack content:\n");
    result = safe_sprintf(buffer, sizeof(buffer), "Hello, %s%d%s%s%s!", "world", 11111, "-1");
    if (result >= 0) {
    printf("Formatted string: %s\n", buffer);
    }
    else {
    printf("Error occurred: %d\n", result);
    }

    printf("Memory Overwrite Example:\n");
    result = safe_sprintf(buffer, sizeof(buffer), "Hello, %s%d%s!", "world", 11111, "Memory Overwrite Memory Overwrite Memory Overwrite", "-1");
    if (result >= 0) {
    printf("Formatted string: %s\n", buffer);
    }
    else {
    printf("Error occurred: %d\n", result);
    }
    return 0;
    }

  2. 运行结果需要截图证明

img

运行结果

img

测试样例

实验中第一次输出成功;

第二次验证可伸展的缓冲区和程序崩溃,报错中断;

第三次验证查看栈内容,报错中断;

第二次验证内容覆写,报错中断;