2009-08-21 38 views
14

Từ C Programming Language 2nd Edition:C: loại chuyển đổi khi đi qua một cuộc tranh cãi trên một cuộc gọi chức năng

Kể từ khi một cuộc tranh cãi của một cuộc gọi chức năng là một biểu hiện, loại chuyển đổi cũng diễn ra khi đối số được truyền cho chức năng. Trong trường hợp không có một nguyên mẫu hàm, char và short trở thành int, và float trở thành gấp đôi.

Bằng cách đọc văn bản, tôi nhận được ấn tượng rằng trừ khi bạn chỉ định rõ ràng loại đối số bằng cách sử dụng mẫu thử nghiệm hoặc hàm, đối số chức năng sẽ luôn được truyền dưới dạng int hoặc double.

Để xác minh giả thiết của tôi, tôi biên soạn đoạn mã sau:

#include <stdio.h> 

main() 
{ 
    unsigned char c = 'Z'; 
    float number = 3.14f; 
    function_call(c, number); 
} 

void function_call(char c, float f) 
{ 
} 

Sau khi biên soạn tôi nhận được những lời cảnh báo sau đây:

typeconversion.c: 11: cảnh báo: mâu thuẫn loại cho ' function_call '

typeconversion.c: 7: warning: khai báo ngầm trước của' function_call 'đã ở đây

Đoán của tôi là c và số được chuyển đổi thành int và double trong lời gọi hàm, và sau đó được chuyển đổi thành char và float. Đây có phải là điều thực sự xảy ra không?

+2

Đây là một câu hỏi hay - và cũng minh họa tại sao điều quan trọng là ** luôn ** có một nguyên mẫu trong phạm vi, với tất cả các thông số khai báo. –

Trả lời

17

Các hành vi không liên quan, đó là nguyên mẫu (có thể tiềm ẩn) quan trọng.

void foo(short s) { 
    // do something 
} 

int main(void) { 
    signed char c = 'a'; 

    foo(c); // c is promoted to short by explicit prototype 
    bar(c); // c is promoted to int by implicit prototype 
} 

void bar(int i) { 
    // do something 
} 

Khi sách nói "đối số của cuộc gọi hàm là biểu thức" có nghĩa là áp dụng cùng loại quy tắc quảng cáo. Có thể dễ hiểu hơn nếu bạn nghĩ về một đối số hàm như một phép gán ngầm cho biến được xác định trong nguyên mẫu hàm. ví dụ. trong cuộc gọi tới foo() ở trên có một số ẩn là short s = c.

Đây là lý do tại sao phôi không quan trọng.Hãy xem xét các đoạn mã sau:

signed char c = 'a'; 
int i = (short) c; 

Ở đây, giá trị của c được đề bạt đầu tiên short (rõ ràng) sau đó đến int (ngầm). Giá trị của i sẽ luôn là int.

Đối với charshort trở intfloat trở double đó đề cập đến các loại mặc định cho nguyên mẫu chức năng tiềm ẩn. Khi trình biên dịch nhìn thấy một cuộc gọi đến một hàm trước khi nó đã nhìn thấy hoặc là một nguyên mẫu hoặc định nghĩa của hàm nó tạo ra một mẫu thử nghiệm tự động. Giá trị mặc định là int cho giá trị số nguyên và double cho các giá trị dấu phẩy động.

Nếu khai báo hàm cuối cùng không khớp với nguyên mẫu ngầm, bạn sẽ nhận được cảnh báo.

+0

bài đăng của bạn trả lời nhiều câu hỏi mà tôi đã có trước khi đăng câu hỏi này. –

+0

Vui mừng được giúp đỡ, Midnight Blue. –

+1

Câu trả lời này trộn lẫn "nguyên mẫu" với "khai báo". Một nguyên mẫu là những gì khai báo kiểu tham số hàm. Một tuyên bố là những gì có thể bao gồm một nguyên mẫu và những gì toàn bộ điều được gọi là ("void f();"). Vì vậy, bạn muốn nói "khai báo ngầm" và "khai báo rõ ràng" thay thế. –

15

Bạn có ý tưởng chung về những gì sai, nhưng không chính xác.

gì đã xảy ra là khi bạn đã viết

function_call(c, number); 

Trình biên dịch thấy rằng bạn đã kêu gọi một chức năng rằng họ đã không thấy nêu ra, và vì vậy đã phải quyết định những gì chữ ký của nó nên được. Dựa trên nguyên tắc khuyến khích bạn trích dẫn ở trên, nó thúc đẩy char sang int và nổi lên gấp đôi và quyết định rằng chữ ký là

function_call(int, double) 

Sau đó, khi nó thấy

function_call(char c, float f) 

nó diễn giải điều này như một chữ ký cho một Chức năng khác với cùng tên, không được cho phép trong C. Đó chính xác là lỗi giống như khi bạn tạo ra một hàm khác với cách bạn định nghĩa nó, chỉ trong trường hợp này nguyên mẫu được tạo ra bởi trình biên dịch.

Vì vậy, nguyên tắc này gây ra sự cố, nhưng lỗi không liên quan đến việc thực sự chuyển đổi các giá trị qua lại giữa các loại.

+0

Quảng bá cũng xảy ra với các khai báo kiểu cũ (K & R), cũng như với các hàm varargs. –

+0

Tôi không nghĩ rằng câu hỏi là về lỗi mỗi lần, mặc dù hai là chắc chắn liên quan. –

+0

Dấu chấm hỏi duy nhất tôi thấy được gắn liền với một câu hỏi liệu việc giải thích của ông về nguyên nhân lỗi là chính xác hay không. Nhưng có, chắc chắn có vài điểm để trả lời trong câu hỏi này. –

2

Trình biên dịch phàn nàn rằng nó giả định function_call là hàm trả về int như được chỉ ra bởi tiêu chuẩn, và sau đó bạn cho biết đó là hàm void. Trình biên dịch sẽ không quan tâm đến các đối số trừ khi bạn tuyên bố rõ ràng chúng khác với hàm thực tế. Bạn có thể vượt qua nó không có đối số và nó sẽ không phàn nàn.

Bạn phải luôn khai báo các hàm của mình vì lỗi này sẽ không bị phát hiện nếu hàm này nằm trong các mô-đun khác. Nếu hàm sẽ trả về bất kỳ loại nào có thể lớn hơn int như void * hoặc dài, thì cast thành int trong hàm người gọi rất có thể sẽ cắt bớt nó, khiến bạn bị lỗi lạ.

+1

Lỗi trình biên dịch không có gì liên quan đến các đối số mà anh ta đang chuyển. Bạn cho rằng nó đã có, bạn đã điều chỉnh tôi. Các câu trả lời khác bỏ qua điều này và làm cho OP nghĩ rằng trình biên dịch của mình sẽ gặp lỗi đối số sai khi nó không xảy ra. Bất cứ điều gì. – jbcreix

2

Mọi người đều đã bỏ lỡ một điều. Trong ISO C, nguyên mẫu cú pháp ISO ghi đè lên khuyến mãi đối số mặc định.

Và trong trường hợp đó trình biên dịch được phép để tạo mã khác (!) dựa hoàn toàn vào kiểu định nghĩa. Điều này giúp bạn tương thích K & R, nhưng bạn không thể luôn gọi giữa các cấp độ ngôn ngữ trừ khi bạn đã viết mã ISO là K & R mong đợi hoặc sửa đổi mã K & R để xem các nguyên mẫu ISO.

Hãy thử nó với cc -S -O ...

extern float q; void f(float a) { q = a; } 
movl 8(%ebp), %eax 
movl %eax, q 

extern float q; void f(a) float a; { q = a; } // Not the same thing! 
fldl 8(%ebp) 
fstps q 
+0

Điều này thật thú vị ... – Artelius

Các vấn đề liên quan