簡體   English   中英

使用 .* 寬度說明符調用 sprintf 時出現奇怪的警告

[英]Strange warning when calling sprintf with .* width specifier

對於以下代碼:

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);
      |     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

事實上,目標緩沖區的大小太小,無法存儲給定 size 參數的值,但是'sprintf' output between 2 and 311 bytes into a destination of size 8什么? 那 311 字節的值從何而來?

如果我將小數位數轉換為int(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)作為精度,以獲得盡可能多的小數,以適應最壞的情況。 很難產生適合所有情況的盡可能多的小數,並且可能需要多次嘗試或復雜的后處理。

那個 311 字節的值是從哪里來的?

編譯器很困惑。

"%.*g", 8可能輸出 15 個*1 + 1(對於空字符)字符,但不能輸出 311。

我懷疑編譯器做出了錯誤的預測,即打印-DBL_MAX將使用 310 + 1 個字符,期望%g切換到極值的指數表示法 - 這就是"%g"的美妙之處,有限的輸出。 如果它沒有切換到指數符號,那么極端輸出將類似於"%.*f", (int) 0

int main(void) {
  char buffer[1000];
  char temp_buffer[8];
  int len = sprintf(buffer, "%.*g", (int) sizeof(temp_buffer), -DBL_MAX);
  printf("%d <%s>\n", len, buffer);
  len = sprintf(buffer, "%.*f", (int) 0, -DBL_MAX);
  printf("%d <%s>\n", len, buffer);
}

輸出

15 <-1.7976931e+308>
310 <-179769313486231570814527423731704356798070565449239578069709514447683386590106403234437238318580897337933920052621128987133479958537240295444402082043505598186819583097828779632178833278408753356733764414284292236482024122876814115690851853178733231033249466834451356736736928870307247739983885979597249860971>

*1

1 1 1   8-1   1 1  3 --> 15
- d . ddddddd e - eee

基本上,它試圖告訴你的是,最終可能會得到比存儲空間更多的數字。 如果 val 是一個非常大的數字會怎樣?

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM