使用 .* 宽度说明符调用 sprintf 时出现奇怪警告

2024-02-01

对于以下代码:

https://godbolt.org/z/WcGf9hEs3 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 flag:

<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 字节的值从哪里来?

如果我将小数位数转换为int, i.e. (int)sizeof(temp_buffer)潜在的溢出数量急剧下降:

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

代码中存在多个问题:

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

Passing sizeof(temp_buffer)是一个错误,编译器似乎对实际参数值感到困惑,并且没有对精度值或要转换的数字做出特定假设。然而,当他们记录输出可以是 2 到 311 字节时,他们似乎是错误的:

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

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

警告程序员这种潜在的未定义行为,干得好!

Use snprintf(),更大的数组并传递(int)(sizeof(temp_buffer) - 9)作为在最坏情况下获得尽可能多的小数的精度。很难产生适合所有情况的尽可能多的小数,并且可能需要多次尝试或复杂的后处理。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用 .* 宽度说明符调用 sprintf 时出现奇怪警告 的相关文章

随机推荐