โปรแกรมนี้ทำงานอย่างไร?


88
#include <stdio.h>

int main() {
    float a = 1234.5f;
    printf("%d\n", a);
    return 0;
}

มันแสดง0!! เป็นไปได้อย่างไร? เหตุผลคืออะไร?


ฉันได้ใส่จงใจ%dในคำสั่งเพื่อศึกษาพฤติกรรมของprintfprintf

คำตอบ:


239

นั่นเป็นเพราะ%dคาดหวังintแต่คุณได้ให้การลอยตัว

ใช้%e/ %f/ %gเพื่อพิมพ์ลอย


เกี่ยวกับเหตุผลที่ 0 จะมีการพิมพ์: จำนวนจุดลอยจะถูกแปลงเป็นก่อนที่จะส่งไปยังdouble printfตัวเลข 1234.5 ในการแสดงสองครั้งใน endian น้อยคือ

00 00 00 00  00 4A 93 40

A %dใช้จำนวนเต็ม 32 บิตดังนั้นจึงพิมพ์ศูนย์ (จากการทดสอบคุณprintf("%d, %d\n", 1234.5f);สามารถได้รับผลลัพธ์0, 1083394560)


เหตุใดจึงfloatถูกแปลงdoubleเป็นต้นแบบของ printf int printf(const char*, ...)จาก 6.5.2.2/7

สัญกรณ์จุดไข่ปลาในตัวประกาศต้นแบบฟังก์ชันทำให้การแปลงประเภทอาร์กิวเมนต์หยุดหลังจากพารามิเตอร์ที่ประกาศครั้งสุดท้าย การส่งเสริมอาร์กิวเมนต์เริ่มต้นจะดำเนินการกับอาร์กิวเมนต์ต่อท้าย

และจาก 6.5.2.2/6

ถ้านิพจน์ที่แสดงถึงฟังก์ชันที่เรียกว่ามีชนิดที่ไม่มีต้นแบบการส่งเสริมจำนวนเต็มจะดำเนินการกับแต่ละอาร์กิวเมนต์และอาร์กิวเมนต์ที่มีชนิดfloatจะถูกเลื่อนdoubleระดับ เหล่านี้จะถูกเรียกว่าโปรโมชั่นเริ่มต้นการโต้แย้ง

(ขอบคุณ Alok ที่ค้นหาสิ่งนี้)


4
+1 คำตอบที่ดีที่สุด มันตอบทั้ง "มาตรฐานที่ถูกต้องทางเทคนิค" ว่าเหตุใดและ "การใช้งานที่เป็นไปได้ของคุณ"
Chris Lutz

12
ฉันเชื่อว่าคุณเป็นเพียงหนึ่งใน 12 คนที่ให้คำตอบที่เขากำลังมองหา
Gabe

11
เนื่องจากprintfเป็นฟังก์ชันแบบแปรผันและมาตรฐานบอกว่าสำหรับฟังก์ชันตัวแปร a floatจะถูกแปลงเป็นdoubleก่อนที่จะส่งผ่าน
Alok Singhal

8
จากมาตรฐาน C: "สัญกรณ์จุดไข่ปลาในตัวประกาศต้นแบบของฟังก์ชันทำให้การแปลงประเภทอาร์กิวเมนต์หยุดหลังจากพารามิเตอร์ที่ประกาศครั้งสุดท้ายการส่งเสริมอาร์กิวเมนต์เริ่มต้นจะดำเนินการกับอาร์กิวเมนต์ต่อท้าย" และ "... และอาร์กิวเมนต์ที่มีประเภท float จะได้รับการเลื่อนระดับเป็นสองเท่าซึ่งเรียกว่าการส่งเสริมอาร์กิวเมนต์เริ่มต้น "
Alok Singhal

2
โดยใช้รูปแบบระบุไม่ถูกต้องในprintf()จะเรียกพฤติกรรมที่ไม่ได้กำหนด
Prasoon Saurav

45

เทคนิคการพูดไม่มีแต่ละดำเนินห้องสมุดของตัวเองและดังนั้นวิธีการที่คุณพยายามที่จะศึกษาพฤติกรรมโดยการทำสิ่งที่คุณทำไม่ได้ไปจะมีการใช้มาก คุณอาจพยายามศึกษาลักษณะการทำงานของระบบของคุณและหากเป็นเช่นนั้นคุณควรอ่านเอกสารประกอบและดูที่ซอร์สโค้ดว่ามีให้สำหรับไลบรารีของคุณหรือไม่ printfprintfprintfprintf

ตัวอย่างเช่นบน Macbook ของฉันฉันได้รับผลลัพธ์1606416304จากโปรแกรมของคุณ

ต้องบอกว่าเมื่อคุณส่งผ่านfloatไปยังฟังก์ชันตัวแปรfloatจะถูกส่งผ่านเป็นไฟล์double. ดังนั้นโปรแกรมของคุณจึงเทียบเท่ากับการประกาศaเป็นไฟล์double.

หากต้องการตรวจสอบไบต์ของ a doubleคุณสามารถดูคำตอบสำหรับคำถามล่าสุดได้ที่ SO

มาทำกัน:

#include <stdio.h>

int main(void)
{
    double a = 1234.5f;
    unsigned char *p = (unsigned char *)&a;
    size_t i;

    printf("size of double: %zu, int: %zu\n", sizeof(double), sizeof(int));
    for (i=0; i < sizeof a; ++i)
        printf("%02x ", p[i]);
    putchar('\n');
    return 0;
}

เมื่อฉันเรียกใช้โปรแกรมข้างต้นฉันจะได้รับ:

size of double: 8, int: 4
00 00 00 00 00 4a 93 40 

ดังนั้นสี่ไบต์แรกdoubleจึงกลายเป็น 0 ซึ่งอาจเป็นสาเหตุที่คุณได้รับ0เป็นผลลัพธ์ของการprintfโทรของคุณ

เพื่อผลลัพธ์ที่น่าสนใจยิ่งขึ้นเราสามารถเปลี่ยนโปรแกรมได้เล็กน้อย:

#include <stdio.h>

int main(void)
{
    double a = 1234.5f;
    int b = 42;

    printf("%d %d\n", a, b);
    return 0;
}

เมื่อฉันเรียกใช้โปรแกรมข้างต้นบน Macbook ของฉันฉันจะได้รับ:

42 1606416384

ด้วยโปรแกรมเดียวกันบนเครื่อง Linux ฉันจะได้รับ:

0 1083394560

ทำไมคุณถึงพิมพ์โปรแกรมย้อนกลับ ฉันพลาดอะไรไปรึเปล่า? ถ้า b เป็น int = 42 และ '% d' เป็นรูปแบบจำนวนเต็มเหตุใดจึงไม่เป็นค่าที่พิมพ์ครั้งที่สองเนื่องจากเป็นตัวแปรที่สองในอาร์กิวเมนต์ printf นี่เป็นการพิมพ์ผิดหรือเปล่า?
Katastic Voyage

อาจเป็นเพราะintอาร์กิวเมนต์ถูกส่งผ่านในรีจิสเตอร์ที่แตกต่างจากdoubleอาร์กิวเมนต์ printfโดย%dใช้intอาร์กิวเมนต์ที่เป็น 42 และครั้งที่สอง%dอาจพิมพ์ขยะเนื่องจากไม่มีintอาร์กิวเมนต์ที่สอง
Alok Singhal

20

ตัว%dระบุบอกprintfให้คาดหวังจำนวนเต็ม ดังนั้นสี่ (หรือสองตัวแรกขึ้นอยู่กับแพลตฟอร์ม) ไบต์ของโฟลตจะถูกตีความเป็นจำนวนเต็ม ถ้าพวกมันเป็นศูนย์จะมีการพิมพ์ศูนย์

การแทนค่าไบนารีของ 1234.5 เป็นสิ่งที่ต้องการ

1.00110100101 * 2^10 (exponent is decimal ...)

ด้วยคอมไพเลอร์ C ซึ่งแทนfloatค่าสองเท่าของ IEEE754 ไบต์จะเป็น (ถ้าฉันไม่ผิดพลาด)

01000000 10010011 01001010 00000000 00000000 00000000 00000000 00000000

ในระบบ Intel (x86) ที่มีความสิ้นสุดเพียงเล็กน้อย (เช่นไบต์ที่มีนัยสำคัญน้อยที่สุดมาก่อน) ลำดับไบต์นี้จะกลับด้านเพื่อให้สี่ไบต์แรกเป็นศูนย์ นั่นคือสิ่งที่printfพิมพ์ออกมา ...

ดูบทความ Wikipedia นี้สำหรับการแสดงจุดลอยตัวตาม IEEE754


7

เป็นเพราะการแทนค่าลอยในไบนารี การแปลงเป็นจำนวนเต็มจะเหลือ 0


1
ดูเหมือนคุณจะเป็นคนเดียวที่เข้าใจสิ่งที่เขาขอ เว้นแต่อิมเข้าใจผิดว่าตัวเองแน่นอน
Mizipzor

ฉันยอมรับว่าคำตอบนั้นไม่ถูกต้องและไม่สมบูรณ์ แต่ก็ไม่ผิด
Shaihi

7

เนื่องจากคุณเรียกใช้พฤติกรรมที่ไม่ได้กำหนด: คุณละเมิดสัญญาของเมธอด printf () โดยโกหกเกี่ยวกับประเภทพารามิเตอร์ดังนั้นคอมไพเลอร์จึงมีอิสระที่จะทำทุกอย่างตามที่ต้องการ มันสามารถทำให้โปรแกรมออกมา "dksjalk is a ninnyhead !!!" และในทางเทคนิคก็ยังคงถูกต้อง


5

เหตุผลก็printf()คือฟังก์ชั่นโง่ ๆ มันไม่ได้ตรวจสอบประเภทเลย หากคุณบอกว่าอาร์กิวเมนต์แรกคือ an int(และนี่คือสิ่งที่คุณกำลังพูดด้วย%d) มันจะเชื่อคุณและใช้เพียงไบต์ที่จำเป็นสำหรับintไฟล์. ในกรณีนี้การพิจารณาเครื่องของคุณใช้สี่ไบต์intและแปดไบต์double( floatถูกแปลงเป็นdoubleด้านในprintf()) สี่ไบต์แรกaจะเป็นเพียงศูนย์และจะได้รับการพิมพ์


3

จะไม่แปลงลอยเป็นจำนวนเต็มโดยอัตโนมัติ เนื่องจากทั้งสองมีรูปแบบการจัดเก็บที่แตกต่างกัน ดังนั้นหากคุณต้องการแปลงให้ใช้ (int) typecasting

#include <stdio.h>

int main() {
    float a = 1234.5f;
    printf("%d\n", (int)a);
    return 0;
}

2

เนื่องจากคุณติดแท็กด้วย C ++ เช่นกันรหัสนี้จึงทำการแปลงตามที่คุณคาดหวัง:

#include <iostream.h>

int main() {
    float a = 1234.5f;
    std::cout << a << " " << (int)a << "\n";
    return 0;
}

เอาท์พุต:

1234.5 1234

1

%d เป็นทศนิยม

%f คือลอย

ดูรายละเอียดของเหล่านี้ที่นี่

คุณจะได้รับ 0 เนื่องจากจำนวนทศนิยมและจำนวนเต็มแสดงต่างกัน


1

คุณเพียงแค่ต้องใช้ตัวระบุรูปแบบที่เหมาะสม (% d,% f,% s ฯลฯ ) กับประเภทข้อมูลที่เกี่ยวข้อง (int, float, string ฯลฯ )


คำถามไม่ใช่วิธีแก้ไข แต่ทำไมมันถึงได้ผล
ya23


0

เฮ้มันต้องพิมพ์อะไรบางอย่างจึงพิมพ์เป็น 0 จำไว้ว่าใน C 0 คืออย่างอื่น


1
มันเป็นอย่างไร? ใน C ไม่มีสิ่งที่เหมือนกับอย่างอื่น
Vlad

ถ้า x คืออะไร x == 0 :)
chunkyguy

ถ้า (iGot == "ทุกอย่าง") พิมพ์"ทุกอย่าง"; อื่นพิมพ์ "nothing";
nik

0

มันไม่ใช่จำนวนเต็ม ลองใช้%f.


ฉันเดาว่าคำถามคือทำไมมันไม่เพียงแค่แปลง float เป็น int และแสดง "1234"?
Björn Pollex

อย่าคาดหวังว่า c จะไม่ปล่อยให้คุณทำบางสิ่งที่ไม่มีเหตุผล ใช่หลายภาษาจะให้ 1234 ที่คุณคาดหวังและบางทีการใช้ c บางอย่างฉันก็ไม่คิดว่าจะกำหนดพฤติกรรมนี้ C ช่วยให้คุณแขวนคอตัวเองเหมือนพ่อแม่ที่ให้คุณพยายามแตกเพื่อนรกของมัน
ใหม่

เนื่องจาก C ถูกออกแบบมาให้มีความยืดหยุ่น
Tangrs
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.