【问题标题】:Is there a portable function like g_printf_string_upper_bound?是否有像 g_printf_string_upper_bound 这样的可移植函数?
【发布时间】:2015-03-31 13:18:26
【问题描述】:

为了生成文件名,我需要为sprintf 提供一些缓冲内存。这些缓冲区的大小在过去是相当随意地选择的。这很容易在将来导致非常讨厌的堆栈溢出错误,例如int 变为 64 位长,但字符串缓冲区大小选择为 10 个字符,因为这是 32 位 int 可以容纳的最大位数。

一些 MWE:

  for (int i = 0; i < mpi_size; i++) {
     //magic number: 32bit integer has 10 digits, 
     //+6 for "/rank_", +1 for null termination
     char path2[strlen(path) + 17];
     //This can possibly be an access violation, or a very hard to
     //find bug:
     sprintf(path2, "%s/rank_%d", path, i);
     //Using path2 to access some file
  }

在其他地方选择的尺寸完全不同,人们非常确定int 不会大于例如3位数。这会更容易导致问题。

什么是完美且便携的解决方案?

我在 gnome 库中找到了函数 g_printf_string_upper_bound,它可以优雅可靠地解决这个问题。

在 C 标准、POSIX 或其他地方有类似的东西吗?

【问题讨论】:

  • snprintf 是 C99 的一部分。
  • 当然,但是结果字符串会是错误的......
  • 你必须检查返回值。如果是size 或更多,则输出被截断。
  • 没错,输出字符串会被截断。这将使我摆脱堆栈损坏,但不会解决问题。

标签: c string printf


【解决方案1】:

尽管被广泛误解,snprintf 是专门为此类情况而设计的。

snprintf 的返回值是如果缓冲区足够大以容纳它,写入的长度(不包括尾随的 NUL)。因此,您可以分两步使用它:使用空缓冲区调用一次以找到所需的长度,使用它分配必要的空间,然后再次调用它以产生结果:

size_t length = snprintf(NULL, 0, "%s/rank_%d", path, i) + 1;

char path2[length];

snprintf(path2, length, "%s/rank_%d", path, i);

至于这是使用snprintf 的预期方式,是的,我很确定确实如此。我是根据与 Peter Seebach 的谈话发表该声明的,他说当他加入 C 标准委员会时,他的主要目的是将 snprintf 纳入标准。

就此而言,我可能不得不为这种方法承担一点责任,我承认这是一种杂耍。早在snprintf 被发明之前,我写了a post on comp.lang.c.moderated,展示了即使没有snprintf 也可以做大致相同的事情。为此,它打开了一个临时文件并将输出写入其中以获取返回值,然后使用malloc 分配缓冲区,最后使用sprintf 将数据放入缓冲区。

snprintf 使用相同的基本思想,但无需打开外部文件即可使用它。尽管如此,它仍然几乎使用了我发布的技术,据我所知,我是第一个提出这种通用方法的人(尽管我很容易相信其他人可能首先想到了它,但太羞于发布它)。

【讨论】:

  • snprintf() 被广泛误解:这里有 3 个问题:snprintf() 返回int,这可能是负数,应该检查。所需的缓冲区大小是length + 1(我看到你现在有了 - 最好在你的代码中使用 1u)。学究式地,第二次调用可能也有麻烦,因为先发制人的语言环境更改(不太可能)或不稳定的参数。
  • @chux:检查负返回值是实际代码所需的错误检查,但为了清楚起见,通常在示例中省略。 +1 已经包含在计算 length 中。更一般地说,snprintf 的基本设计确实不能很好地处理并发性。
  • 看了参考和代码,相信这确实是snprintf()的设计意图。谢谢! @chux:只有在编码错误的情况下才会返回负值。当然,应该始终检查所有可能的错误情况,但这对我来说并不构成对该功能的广泛误解(这是一个示例)。缓冲区大小始终为snprintf(...) + 1,所以这是正确的。在这些调用之间更改变量值显然很糟糕。
【解决方案2】:

考虑asprintf()。它将根据需要分配需要的空间。唯一预期的失败是内存不足。它不是标准的 C 函数,但可用于许多 *nix 系统。否则:

建议2个步骤:

  1. 根据其参数将缓冲区缩放到预期的最大输出大小。几个字节太大应该没问题。

    // e.g. size needed to print INT_MIN
    #define INT_SIZE_MAX (sizeof(int)*CHAR_BIT/3 + 3)
    
    const char rank[] = "/rank_";
    char path2[strlen(path) + sizeof rank + INT_SIZE_MAX + 1];
    sprintf(path2, "%s%s%d", path, rank, i);
    
  2. 使用snprinf()确保不会溢出

    int n = snprintf(path2, sizeof path2, "%s%s%d", path, rank, i);
    if (n >= sizeof path2 || n < 0) HandleRareFailure();
    

snprintf() 可能失败的原因:
1) 某些字节序列无效(编码错误)。
2) 语言环境更改导致逗号添加到 %d --> "1,234,567,890"。
3) 月相。见下文 cmets。

【讨论】:

  • 月相是什么
  • @Lightness Races in Orbit 一个迂腐的观念认为snprintf(),一个相当复杂的函数,考虑到其潜在的扩展和语言环境问题,可能会因看似不寻常的数据或相关性(如“月相”)而失败.
  • 我可以向你保证,snprintf() 的行为与月相无关。
  • @Lightness Races in Orbit 我承认snprintf() 不依赖于“月相”的“轨道竞赛”。
  • 不错的答案!谢谢! asprintf() 看起来很有希望。不过,我需要调查它对于我的用例是否足够便携。感谢您的第二个建议及其实施。它有一些问题(%16d、其他数据类型等)并且看起来相当笨拙。我绝对理解“月相”位。
【解决方案3】:

在 C 标准中,[或]在 POSIX 中是否有类似的东西?

没有。

还是其他地方?

嗯,在 Gnome 库中有类似的东西……:P

【讨论】:

  • 您似乎知道答案但不想发布它。不好。
  • @anatolyg:嗯,什么?这的答案。有什么问题?
  • 哦,我不明白这是对g_printf_string_upper_bound的引用!不过,这不是一个有用的答案。
  • @anatolyg:在什么方面没有用?问题是:在 C 标准或 POSIX 中是否有这样的功能。那没有。所以,答案是“不”。
  • 正确答案是“是”,有问题的函数是snprintf。所以你的回答不仅没有帮助,而且是完全错误的。
猜你喜欢
  • 2013-11-12
  • 1970-01-01
  • 2021-02-02
  • 1970-01-01
  • 2015-12-06
  • 2021-03-28
  • 1970-01-01
  • 2023-03-06
  • 2021-05-13
相关资源
最近更新 更多