[英]Unexpected printf behaviour in c, printing float as hex changes value on each run
I wrote some c code to play around with float values in memory, but ended up getting some unexpected output from printf compiling with "gcc (GCC) 12.1.1 20220730" with -std=c11 option.
我不知道它為什么會這樣,並且想知道發生了什么,如果我做錯了什么,以及如何將其轉換為 printf 一個浮點值作為十六進制,如果可能的話,無需先將其轉換為另一種類型?
這是使用的代碼和 output 的不同運行。
代碼:
#include <stdio.h>
int main()
{
float f3 = 1.1;
float f4 = 1.0;
unsigned char *t1 = &f3;
unsigned *t2 = &f3;
printf("P1: %x\n", t2[0]);
printf("P2: %x %x %x %x\n", t1[0], t1[1], t1[2], t1[3]);
printf("P3: %p\n", &f3);
printf("P4: %lx, %lx, %llx\n", &f3, f3, f4);
printf("T1: %f, %f, %lx, %lx\n", f3, f4, f4, f3);
printf("T2: %x, %lx\n", f4, f3);
printf("T3: %x, %lx\n", f3, f4);
return 0;
}
主要問題似乎是將浮點數打印為十六進制:
printf("%x\n", f3);
Output 1:
P1: 3f8ccccd // as expected
P2: cd cc 8c 3f // as expected
P3: 0x7ffc667d4d40 // as expected
P4: 7ffc667d4d40, 3ff19999a0000000, 0 // pointer value as expected, but second and third isn't. Values stay the same after each run
T1: 1.100000, 1.000000, 5556db09b2a0, 0 // first two values as expected, second and third isn't, Values do not stay the same after each run
T2: db09b2a0, 0 // this value keeps changing after each run
T3: db09b2a0, 0 // same as above, but should be different? Also changes after each run.
Output 2:
P1: 3f8ccccd
P2: cd cc 8c 3f
P3: 0x7ffef87ebb00
P4: 7ffef87ebb00, 3ff19999a0000000, 0
T1: 1.100000, 1.000000, 55fb6a1962a0, 0
T2: 6a1962a0, 0
T3: 6a1962a0, 0
Output 3:
P1: 3f8ccccd
P2: cd cc 8c 3f
P3: 0x7ffdb2026640
P4: 7ffdb2026640, 3ff19999a0000000, 0
T1: 1.100000, 1.000000, 564dd210c2a0, 0
T2: d210c2a0, 0
T3: d210c2a0, 0
主要問題似乎是將浮點數打印為十六進制:
printf("%x\n", f3);
是的,因為%x
格式說明符需要一個unsigned int
作為參數,但是您傳入的float
被提升為double
。
使用錯誤的格式說明符會觸發未定義的行為,在這種情況下表現為奇怪的 output。 至於幕后發生的事情,浮點值通常使用浮點寄存器傳遞給 function,而 integer 值通常被推入堆棧。
這也是無效的:
printf("P1: %x\n", t2[0]);
因為它會導致嚴格的混疊違規。 這基本上意味着您不能像訪問另一種類型一樣訪問一種類型的字節,除非目標類型是char
或unsigned char
。
打印浮點類型的字節表示的正確方法是讓unsigned char *
指向第一個字節,然后遍歷字節並打印每個字節。
在所有“意外輸出”的情況下,您傳遞的參數的類型與格式說明符不匹配。 你告訴printf()
期待一件事,但傳遞另一件事。 那總是未定義的行為,推測特定的 output 是如何產生的在很大程度上是沒有意義的。 此外,將float
傳遞給printf()
會將其提升為 double - 因此您嘗試檢查的表示形式將與原始float
變量的表示形式不同。
printf()
也在運行時處理這些類型 - 編譯器的類型檢查在這里無法幫助您,適用的是printf()
的規則,而不是編譯器的 - 它是可變參數 function,就編譯器而言可以采用任意數量的任意類型的 arguments。 也就是說,許多編譯器會檢查 stdio 格式說明符,因為它們是標准庫中定義明確的部分——您可能需要啟用特定警告或更高的警告級別才能啟用編譯器的檢查。
通常要檢查代表浮點值的位,您需要獲取浮點的地址,將該地址轉換為相同寬度的 integer 指針,然后取消引用它。
然而,嚴格的別名規則使得(嚴格)未定義,因此雖然*(uint32_t*)&f1
很可能會產生預期的 integer 值,但您不能依賴它。
一個定義明確的解決方案是將字節作為unsigned char
訪問,或者將字節復制(例如使用memcpy()
)到相同大小的 integer object 。
例如:
#include <stdio.h>
#include <inttypes.h>
#include <stdint.h>
#include <string.h>
int main()
{
float f1 = 1.0f ;
float f2 = 1.1f ;
uint32_t b1 = 0u ;
uint32_t b2 = 0u ;
memcpy( &b1, &f1, sizeof(b1) ) ;
memcpy( &b2, &f2, sizeof(b2) ) ;
printf( "f1: %f @%p = %"PRIx32" (%02x %02x %02x %02x)\n",
f1, &f1, b1,
((unsigned char*)&f1)[0],
((unsigned char*)&f1)[1],
((unsigned char*)&f1)[2],
((unsigned char*)&f1)[3] ) ;
printf( "f2: %f @%p = %"PRIx32" (%02x %02x %02x %02x)\n",
f2, &f2, b2,
((unsigned char*)&f2)[0],
((unsigned char*)&f2)[1],
((unsigned char*)&f2)[2],
((unsigned char*)&f2)[3] ) ;
return 0;
}
輸出:
f1: 1.000000 @0x7fffe3e06668 = 3f800000 (00 00 80 3f)
f2: 1.100000 @0x7fffe3e0666c = 3f8ccccd (cd cc 8c 3f)
(顯然地址會有所不同)。
您還可以通過聯合來實現類型雙關:
typedef union
{
float f ;
uint32_t u ;
} uFloatIntPun ;
然后你可以例如:
uFloatIntPun pun ;
pun.f = f1 ;
printf( "%"PRIx32"\n", pun.u ) ;
當然,如果您只想檢查特定變量的位置和內部表示以及字節順序,那么在符號調試器中觀察它們要簡單得多且不易出錯。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.