对于以下代码:

https://godbolt.org/z/WcGf9hEs3

#include <stdio.h>

int main() { 
    
    char temp_buffer[8];
    double val = 25.3;

    sprintf(temp_buffer, "%.*g", sizeof(temp_buffer), val);
    printf("%s", temp_buffer);
}

我在gcc 11.3中得到了带有-Wall个标志的警告:

<source>:8:29: warning: field precision specifier '.*' expects argument of type 'int', but argument 3 has type 'long unsigned int' [-Wformat=]
    8 |     sprintf(temp_buffer, "%.*g", sizeof(temp_buffer), val);
      |                           ~~^~   ~~~~~~~~~~~~~~~~~~~
      |                             |    |
      |                             int  long unsigned int
<source>:8:27: warning: '%.*g' directive writing between 1 and 310 bytes into a region of size 8 [-Wformat-overflow=]
    8 |     sprintf(temp_buffer, "%.*g", sizeof(temp_buffer), val);
      |                           ^~~~
<source>:8:26: note: assuming directive output of 12 bytes
    8 |     sprintf(temp_buffer, "%.*g", sizeof(temp_buffer), val);
      |                          ^~~~~~
<source>:8:5: note: 'sprintf' output between 2 and 311 bytes into a destination of size 8
    8 |     sprintf(temp_buffer, "%.*g", sizeof(temp_buffer), val);
      |     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

事实上,目标缓冲区的大小太小,无法存储给定大小参数的值,但是警告'sprintf' output between 2 and 311 bytes into a destination of size 8是怎么回事?311字节的值来自哪里?

如果我将小数点的位数设为inti.e.(int)sizeof(temp_buffer),则潜在的溢出数会急剧下降:

'sprintf' output between 2 and 16 bytes into a destination of size 8

推荐答案

代码中有多个问题:

  • sprintf期望*位的值为int,而您传递的size_t可能具有不同的大小和表示形式.
  • 调用可能有未定义的行为,因为您请求的精度是目标数组的长度,这可能会产生超过所述长度的输出.

如果超过sizeof(temp_buffer)是一个错误,编译器似乎会对实际参数值感到困惑,并且不会对精度值或要转换的数字做出特定的假设.然而,当他们记录输出可能是2到311字节时,他们似乎错了:

  • 对于值25.3,最接近的IEEE 754数字的精确表示为25.300000000000000710542735760100185871124267578125,需要52字节.
  • 通过printf("%.1000g", -0x1.fffffffffffffp+1023)输出的最大数字有310个字符,因此需要311字节,这似乎是2 to 311 bytes的原因.
  • 然而,%.*g次转换实际上可以产生超过311个字节:printf("%.1000g", -5e-324)在macOS和linux上产生758个字符.

当您将sizeof(temp_buffer)转换为(int)时,编译器确定精度为8(一个非常重要的优化),并确定输出可以小到2字节(一个数字和一个空终止符),但不超过16字节(-,一个数字,.,7位小数,e-,以及多达3个指数数字加上一个空终止符.对于8字节数组来说,这可能仍然太多.

很好地警告了程序员这个潜在的未定义行为!

使用snprintf(),一个更大的数组,并通过(int)(sizeof(temp_buffer) - 9)作为精度,以获得尽可能多的小数,将适合在最坏的情况下.很难在所有情况下产生尽可能多的小数,可能需要多次try 或复杂的后处理.

C++相关问答推荐

从STdin读写超过4096个字节

为什么信号量为空= 0,而不是阻塞?

非常大的数组的大小

在C++中使用函数指针的正确语法

函数内的局部字符指针

在C++中访问双指针

在for循环中指向数组开头之前

不同出处的指针可以相等吗?

类型定义 struct 与简单的类型定义 struct

C语言中MPI发送接收字符串时出现的分段错误

用C++构建和使用DLL的困惑

Zlib:解压缩大文件导致";无效代码长度设置";错误

C:Assignment中的链表赋值从指针目标类型中丢弃‘const’限定符

按字典顺序打印具有给定字符的所有可能字符串

我可以使用Windows SDK';s IN6_IS_ADDR_LOOPBACK等,尽管没有文档?

如何使用 raylib 显示数组中的图像

Zig 中 C 的system函数的惯用替代方案

我如何(合规地)从 tmpfile() 读取?

c中数组上显示的随机元素

在 atmega4809 上使用 UART1 发送多个字符会导致发送 0xFF