Chương trình này hoạt động như thế nào?


88
#include <stdio.h>

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

Nó hiển thị một 0!! Làm thế nào là có thể? Lý luận là gì?


Tôi đã cố tình đặt một %dtrong các printftuyên bố nghiên cứu hành vi của printf.

Câu trả lời:


239

Đó là bởi vì %dmong đợi một intnhưng bạn đã cung cấp một phao.

Sử dụng %e/ %f/ %gđể in phao.


Tại sao số 0 được in: Số dấu phẩy động được chuyển đổi thành doubletrước khi gửi đến printf. Số 1234,5 trong biểu diễn kép ở số cuối nhỏ là

00 00 00 00  00 4A 93 40

A %dsử dụng một số nguyên 32 bit, vì vậy số 0 được in. (Như một bài kiểm tra, bạn có thể printf("%d, %d\n", 1234.5f);Bạn có thể nhận được đầu ra 0, 1083394560.)


Đối với lý do tại sao floatchuyển đổi thành double, như nguyên mẫu của printf int printf(const char*, ...), từ 6.5.2.2/7,

Kí hiệu dấu chấm lửng trong bộ khai báo nguyên mẫu hàm khiến việc chuyển đổi kiểu đối số dừng lại sau tham số được khai báo cuối cùng. Việc thăng hạng đối số mặc định được thực hiện trên các đối số theo sau.

và từ 6.5.2.2/6,

Nếu biểu thức biểu thị hàm được gọi có kiểu không bao gồm nguyên mẫu, thì việc thăng hạng số nguyên được thực hiện trên mỗi đối số và các đối số có kiểu floatđược thăng cấp double. Chúng được gọi là quảng cáo đối số mặc định .

(Cảm ơn Alok đã tìm ra điều này.)


4
+1 Câu trả lời hay nhất. Nó trả lời cả lý do "tiêu chuẩn chính xác về mặt kỹ thuật" và "khả năng triển khai của bạn" tại sao.
Chris Lutz

12
Tôi tin rằng bạn là người duy nhất trong số 12 người thực sự cung cấp câu trả lời mà anh ấy đang tìm kiếm.
Gabe

11
Bởi vì printflà một hàm biến thiên, và tiêu chuẩn nói rằng đối với các hàm biến thiên, a floatđược chuyển đổi thành doubletrước khi chuyển.
Alok Singhal

8
Từ tiêu chuẩn C: "Ký hiệu dấu chấm lửng trong trình khai báo nguyên mẫu hàm khiến quá trình chuyển đổi kiểu đối số dừng lại sau tham số được khai báo cuối cùng. Quảng cáo đối số mặc định được thực hiện trên đối số ở cuối." và "... và các đối số có kiểu float được thăng cấp lên gấp đôi. Chúng được gọi là các thăng hạng đối số mặc định ."
Alok Singhal

2
Sử dụng công cụ chỉ định định dạng không chính xác trong việc printf()gọi Hành vi không xác định .
Prasoon Saurav

45

Về mặt kỹ thuật không có sự printf , mỗi thư viện cụ riêng của mình, và do đó, phương pháp của bạn cố gắng nghiên cứu printfcủa hành vi bằng cách thực hiện những gì bạn đang làm sẽ không được sử dụng nhiều. Bạn có thể đang cố gắng nghiên cứu hoạt động của printfhệ thống của mình và nếu vậy, bạn nên đọc tài liệu và xem mã nguồn printfxem nó có sẵn cho thư viện của bạn hay không.

Ví dụ: trên Macbook của tôi, tôi nhận được đầu ra 1606416304bằng chương trình của bạn.

Phải nói rằng, khi bạn chuyển một hàm floatcho một hàm variadic, hàm floatđược chuyển dưới dạng a double. Vì vậy, chương trình của bạn tương đương với việc khai báo alà a double.

Để kiểm tra các byte của a double, bạn có thể xem câu trả lời này cho câu hỏi gần đây trên SO.

Hãy làm điều đó:

#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;
}

Khi tôi chạy chương trình trên, tôi nhận được:

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

Vì vậy, bốn byte đầu tiên doublehóa ra là 0, đó có thể là lý do tại sao bạn nhận được 0như là đầu ra của printfcuộc gọi của mình .

Để có kết quả thú vị hơn, chúng ta có thể thay đổi chương trình một chút:

#include <stdio.h>

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

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

Khi tôi chạy chương trình trên trên Macbook của mình, tôi nhận được:

42 1606416384

Với cùng một chương trình trên máy Linux, tôi nhận được:

0 1083394560

Tại sao bạn lập trình in ngược? Tui bỏ lỡ điều gì vậy? Nếu b là int = 42 và '% d' là định dạng số nguyên, tại sao nó không phải là giá trị in thứ hai, vì nó là biến thứ hai trong các đối số printf? Đây có phải là lỗi đánh máy không?
Katastic Voyage

Có thể là do các intđối số được truyền trong các thanh ghi khác với các doubleđối số. printfvới %dlấy intđối số là 42 và đối số thứ hai %dcó thể in rác vì không có intđối số thứ hai .
Alok Singhal

20

Bộ %dchỉ định cho biết printfmong đợi một số nguyên. Vì vậy, bốn (hoặc hai, tùy thuộc vào nền tảng) byte đầu tiên của float được intepreted như một số nguyên. Nếu chúng bằng 0, số 0 sẽ được in

Biểu diễn nhị phân của 1234.5 giống như

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

Với trình biên dịch C đại diện floatthực sự là các giá trị kép IEEE754, các byte sẽ là (nếu tôi không mắc lỗi)

01000000 10010011 01001010 00000000 00000000 00000000 00000000 00000000

Trên hệ thống Intel (x86) có ít nội dung (tức là byte ít quan trọng nhất đến trước), chuỗi byte này được đảo ngược để bốn byte đầu tiên bằng 0. Đó là, những gì printfin ra ...

Xem bài viết này trên Wikipedia về biểu diễn dấu phẩy động theo IEEE754.


7

Đó là vì sự biểu diễn của một float trong hệ nhị phân. Việc chuyển đổi thành một số nguyên để lại nó bằng 0.


1
Bạn dường như là người duy nhất hiểu những gì anh ấy đang yêu cầu. Tất nhiên trừ khi tôi nhầm lẫn bản thân mình.
Mizipzor

Tôi đồng ý rằng câu trả lời là không chính xác và không đầy đủ, nhưng không sai.
Shaihi

7

Bởi vì bạn đã gọi hành vi không xác định: bạn đã vi phạm hợp đồng của phương thức printf () bằng cách nói dối nó về các loại tham số của nó, vì vậy trình biên dịch có thể tự do làm bất cứ điều gì nó muốn. Nó có thể làm cho đầu ra chương trình "dksjalk is a ninnyhead !!!" và về mặt kỹ thuật thì nó vẫn đúng.


5

Lý do là đó printf()là một chức năng khá ngu ngốc. Nó không kiểm tra các loại ở tất cả. Nếu bạn nói đối số đầu tiên là một int(và đây là những gì bạn đang nói với %d), nó tin bạn và nó chỉ cần số byte cần thiết cho một int. Trong trường hợp này, khi máy tính của bạn sử dụng bốn byte intvà tám byte double( floatđược chuyển đổi thành doublebên trong printf()), bốn byte đầu tiên asẽ chỉ là số 0 và điều này sẽ được in.


3

Nó sẽ không tự động chuyển đổi float thành số nguyên. Bởi vì cả hai đều có định dạng lưu trữ khác nhau. Vì vậy, nếu bạn muốn chuyển đổi, hãy sử dụng (int) typecasting.

#include <stdio.h>

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

2

Vì bạn cũng đã gắn thẻ nó bằng C ++ nên mã này thực hiện chuyển đổi như bạn có thể mong đợi:

#include <iostream.h>

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

Đầu ra:

1234.5 1234

1

%d là số thập phân

%f là phao

xem thêm những thứ này ở đây .

Bạn đang nhận được 0 vì số thực và số nguyên được biểu diễn khác nhau.


1

Bạn chỉ cần sử dụng mã định dạng thích hợp (% d,% f,% s, v.v.) với kiểu dữ liệu có liên quan (int, float, string, v.v.).


Câu hỏi không phải là làm thế nào để sửa chữa nó mà tại sao nó hoạt động theo cách nó hoạt động.
ya23


0

hey nó đã phải in một cái gì đó vì vậy nó in một 0. Hãy nhớ rằng trong C 0 là mọi thứ khác!


1
Làm thế nào là nó như vậy? Trong C không có thứ gì giống như mọi thứ khác.
Vlad

nếu x là cái gì đó thì! x == 0 :)
chunkyguy

if (iGot == "mọi thứ") in "mọi thứ"; else in "nothing";
nik

0

Nó không phải là một số nguyên. Hãy thử sử dụng %f.


Tôi đoán câu hỏi là, tại sao nó không chuyển đổi float thành int và hiển thị "1234"?
Björn Pollex

đừng mong c sẽ không để bạn làm điều gì đó không hợp lý. Có nhiều ngôn ngữ sẽ cung cấp 1234 mà bạn có thể mong đợi, và thậm chí có thể một số triển khai của c tôi sẽ không nghĩ rằng hành vi này được định nghĩa. C cho phép bạn tự treo cổ nó giống như cha mẹ cho phép bạn thử crack cho cái quái quỷ của nó.
chạy lại

Vì C được thiết kế để linh hoạt.
tangrs
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.