Cpp check show Buffer 'tmpf' 在它的旧内容被使用之前被写入

Cpp check show Buffer 'tmpf' is being written before its old content has been used

这个函数在 cpp 检查时给我一个警告(在第 5 行)。我不知道这背后的原因,也不知道它是否值得修复。

static char *get_file_name(const char *fmt, long n, int has_fmt_spec)
{
    static char tmpf[4096];
    int bytes;
    bytes = has_fmt_spec ? snprintf(tmpf, 4095, fmt, n) : 
        snprintf(tmpf, 4095, "%s%ld", fmt, n);<--- Buffer 'tmpf' is being written before its old content has been used.
    if (bytes >= 4095) {
        printf("file name too long\n");
        exit(EXIT_FAILURE);
    }
    return tmpf;
}

一些反馈:

  1. ᴅᴏɴ'ᴛʀᴇᴛᴜʀɴᴏꜰꜱᴛᴀᴛɪᴄꜰʀᴏᴍꜰᴜɴᴄᴛɪᴏɴ:避免return在函数中声明的静态缓冲液指针。它颠倒了作用域层次结构并且它是不可重入的(例如,不是线程安全的)。它很容易出错,因为该函数会覆盖其 returning 指向的缓冲区,该缓冲区可能由另一个线程(甚至同一个线程)持有或使用。人们很容易忘记内存是易失性的,这会导致一些非常隐蔽的问题,即使不是立即诊断,在代码的持续开发或扩展的某个时刻也很难诊断。

  2. ʀᴇᴛᴜʀɴʀᴇᴛᴜʀɴʜᴇᴀᴩꜰʀᴏᴍꜰᴜɴᴄᴛɪᴏɴ:从堆分配内存和return指针,让呼叫者免费,是更普遍的它。在这种情况下,缓冲区大小不需要固定为任意大小,因为 snprintf() 可以让您计算存储结果所需的长度;这样您就可以分配正确数量的内存,从而避免检查缓冲区溢出的需要。理想情况下,结果缓冲区大小应仅受提供的参数大小和可用内存的限制。该示例显示了从函数传播堆指针的两种方法。一种方法是 return 将其作为函数的 return 值;另一种方式通过函数参数传递它。

  3. ᴀᴠᴏɪᴅ ᴛᴇʀᴍɪɴᴀᴛɪɴɢ ᴩʀᴏɢʀᴀᴍ ɪɴ ᴀ ꜰᴜɴᴄᴛɪᴏɴ: 退出函数通常是一个坏主意最好对错误处理进行策略性处理,将潜在的致命错误传播到 main() 必要时从那里退出,如果错误是不可恢复的并且退出对于应用程序的性质来说是可以接受的(对于像烤面包机这样的嵌入式系统烤箱,或控制塔雷达,它不是)。客户讨厌应用程序意外退出,这可能会产生严重的影响。不要让它在任意函数中发生不可预见的小错误。

  4. ᴇʀʀᴏʀ ʜᴀɴᴅʟɪɴɢɪɴ ꜰᴜɴᴄᴛɪᴏɴ 下面示例中的错误处理是足够的,但很少。在 main() 级别,示例代码假定函数的任何错误只能是 'out of memory'。另一种处理方法可能是 return 多个全局错误特定常量之一(通过枚举,#defineconst),或 return 一条错误消息通过另一个 char * 参数,或从函数内部记录错误细节并传递通用失败代码,并让调用者处理该条件(如果无法处理,它也会向上传递)。注意:示例中使用的 atoi() 可能会失败(即,如果它无法解析给定的字符串),但如果是 returns 0,那么对于这个示例来说已经足够好了。重点是 - 始终检查 return 值 and/or 有一个构思周密的错误处理策略 。许多错误是由于看似无害的函数的意外失败而发生的,如果 return 值不是 checked/handled,则很难找到。

  5. ᴄᴏɴꜱɪᴅᴇʀ ᴀ ᴍᴏʀᴇ ɢᴇɴᴇʀɪᴄ ꜱᴏʟᴜᴛɪᴏɴ: 创建可以处理任意情况的通用解决方案通常很有用。如果我这样做,我通常更喜欢一个可以处理任何格式说明符和任意参数列表的函数。但对于这个例子来说,这太过分了。

    #include <stdio.h>
    
    /* 
     * This variant of the function returns pointer 
     * via the function return value.  Caller frees.
     */
    static char *getFilename1(const char *fmt, long n)
    {
        size_t size = snprintf(NULL, 0, fmt, n);
        char *buf = malloc(size);
        if (buf != null)
            snprintf(buf, size, fmt, n);
        return buf;
    }
    
    /* 
     * This variant of the function returns the pointer 
     * via a function argument.  Caller frees.
     */
    static int getFilename2(char **bufp, const char *fmt, long n)
    {
        size_t size = snprintf(NULL, 0, fmt, n);
        if ((*buf = malloc(size)) != null) {
            snprintf(buf, size, fmt, n);
            return 0;
        }
        return -1;
    }
    
    int
    main(int argc, char **argv) 
    {
        char *filename;
        int n = 0;
    
        if (argc > 1) 
            n = atoi(argv[1]);
    
        /* Obtaining buffer pointer as return value */
    
        if ((filename = getFilename1("file#%d", n)) == null) {
             fprintf(stderr, "Out of memory");
             return EXIT_FAILURE;
        }
        printf("Filename = %s\n", filename);
        free(filename);
    
        /* Obtaining pointer via function argument */
    
        if ((getFilename2(&filename, "file#%d", n) < 0) {
             fprintf(stderr, "Out of memory");
             return EXIT_FAILURE;
        }
        printf("Filename = %s\n", filename);
        free(filename);
    
        return EXIT_SUCCESS;
    }