2011-09-02 22 views
9
#include <stdio.h> 

char toUpper(char); 

int main(void) 
{ 
    char ch, ch2; 
    printf("lowercase input : "); 
    ch = getchar(); 
    ch2 = toUpper(ch); 
    printf("%c ==> %c\n", ch, ch2); 

    return 0; 
} 

char toUpper(char c) 
{ 
    if(c>='a'&&c<='z') 
     c = c - 32; 
} 

Trong hàm ToUpper, kiểu trả về là char, nhưng không có "return" trong toUpper(). Và biên dịch mã nguồn bằng gcc (GCC) 4.5.1 20100924 (Red Hat 4.5.1-4), fedora-14.Tại sao và làm thế nào để GCC biên dịch một hàm có câu lệnh trả về bị thiếu?

Tất nhiên, cảnh báo được đưa ra: "cảnh báo: điều khiển đến hết chức năng không có hiệu lực", nhưng hoạt động tốt.

Điều gì đã xảy ra trong mã đó trong khi biên dịch với gcc? Tôi muốn nhận được câu trả lời chắc chắn trong trường hợp này. Cảm ơn :)

+5

Mùi giống như hành vi không xác định. – ThiefMaster

+2

@ThiefMaster: Nó ** là ** UB. Anh ta chỉ may mắn rằng thanh ghi trong đó giá trị trả về thường được đặt xảy ra cũng được sử dụng cho phép trừ. –

+0

Cảm ơn tất cả mọi người :) – harrison

Trả lời

19

gì đã xảy ra cho bạn là khi chương trình C được biên dịch thành ngôn ngữ lắp ráp, chức năng toUpper của bạn đã kết thúc như thế này, có lẽ:

_toUpper: 
LFB4: 
     pushq %rbp 
LCFI3: 
     movq %rsp, %rbp 
LCFI4: 
     movb %dil, -4(%rbp) 
     cmpb $96, -4(%rbp) 
     jle  L8 
     cmpb $122, -4(%rbp) 
     jg  L8 
     movzbl -4(%rbp), %eax 
     subl $32, %eax 
     movb %al, -4(%rbp) 
L8: 
     leave 
     ret 

Các phép trừ 32 được tiến hành vào sổ đăng ký% eax. Và trong quy ước gọi x86, đó là thanh ghi trong đó giá trị trả về được mong đợi! Vì vậy ... bạn đã may mắn.

Nhưng hãy chú ý đến các cảnh báo. Họ ở đó vì một lý do!

+0

+1 cho hiển thị asm –

+1

Đây chính xác là những gì tôi đoán: eax được sử dụng, chứa kết quả của phép toán và eax là giá trị trả về. Nhưng cậu bé, rất khó để đọc hội đồng kiểu GNU khi bạn quen với phong cách của Intel. –

+0

Bây giờ tôi đã biết vấn đề này là gì. Tôi thấy cảnh báo nhưng tôi tự hỏi. Cảm ơn :) – harrison

7

Tùy thuộc vào số Application Binary Interface và sổ đăng ký nào được sử dụng để tính toán.

Ví dụ: trên x86, tham số hàm đầu tiên và giá trị trả về được lưu trữ trong EAX và do đó gcc rất có thể sử dụng điều này để lưu trữ kết quả tính toán.

+0

Tôi đã đọc quy ước ABI và gọi điện thoại. Cảm ơn :) – harrison

1

Tôi không thể cho bạn biết chi tiết về nền tảng của bạn vì tôi không biết nhưng có một câu trả lời chung cho hành vi bạn thấy.

Khi một số hàm có trả về được biên dịch, trình biên dịch sẽ sử dụng quy ước về cách trả về dữ liệu đó. Nó có thể là một máy tính đăng ký, hoặc một vị trí bộ nhớ được xác định như thông qua một ngăn xếp hoặc bất cứ điều gì (mặc dù thường đăng ký máy được sử dụng). Mã được biên dịch cũng có thể sử dụng vị trí đó (đăng ký hoặc cách khác) trong khi thực hiện công việc của hàm.

Nếu hàm không trả về bất kỳ thứ gì, trình biên dịch sẽ không tạo mã để lấp đầy vị trí đó với giá trị trả về một cách rõ ràng. Tuy nhiên như tôi đã nói ở trên, nó có thể sử dụng vị trí đó trong suốt hàm. Khi bạn viết mã đọc giá trị trả về (ch2 = toUpper(ch);), trình biên dịch sẽ viết mã sử dụng quy ước của nó về cách lấy trả về từ vị trí thông thường. Theo như mã người gọi là có liên quan, nó sẽ chỉ đọc giá trị đó từ vị trí, ngay cả khi không có gì được viết rõ ràng ở đó. Do đó bạn nhận được một giá trị.

Bây giờ hãy xem ví dụ của @ Ray, trình biên dịch đã sử dụng thanh ghi EAX, để lưu trữ kết quả của hoạt động vỏ trên. Nó chỉ xảy ra, đây có lẽ là vị trí trả về giá trị được ghi vào. Trên ch2 bên gọi được nạp với giá trị trong EAX - do đó trở về phantom. Điều này chỉ đúng với phạm vi xử lý x86, như trên các kiến ​​trúc khác, trình biên dịch có thể sử dụng một lược đồ hoàn toàn khác để quyết định cách thức tổ chức hội nghị

Tuy nhiên trình biên dịch tốt sẽ cố gắng tối ưu hóa theo các điều kiện địa phương, kiến ​​thức mã, quy tắc và phỏng đoán. Vì vậy, một điều quan trọng cần lưu ý là đây chỉ là may mắn mà nó hoạt động. Trình biên dịch có thể tối ưu hóa và không làm điều này hoặc bất cứ điều gì - bạn không nên trả lời về hành vi.

+0

-1 giống như một tập tài sản Trung Quốc ... cụ thể hơn – Quamis

+0

Chính xác thì bạn muốn tôi cụ thể hơn về điều gì? –

+0

rút lại -1 của tôi :) – Quamis

2

Về cơ bản, c được đẩy vào vị trí mà sau đó sẽ được lấp đầy với giá trị trả lại; vì nó không được ghi đè bằng cách sử dụng return, nó kết thúc bằng giá trị trả về.

Lưu ý rằng dựa vào điều này (trong C, hoặc bất kỳ ngôn ngữ nào khác mà đây không phải là một tính năng ngôn ngữ rõ ràng, như Perl), là một Bad Idea ™. Trong cực đoan.

+0

câu trả lời là% đăng ký eax. Tôi đã nhận được nó – harrison

2

Một điều còn thiếu quan trọng cần hiểu là hiếm khi có lỗi chẩn đoán để bỏ qua câu lệnh trả về. Xem xét chức năng này:

int f(int x) 
{ 
    if (x!=42) return x*x; 
} 

Chừng nào bạn không bao giờ gọi nó với một cuộc tranh cãi của 42, một chương trình có chứa chức năng này là hoàn toàn hợp lệ C và không gọi bất kỳ hành vi không xác định, mặc dù thực tế là nó sẽ invoke UB nếu bạn gọi là f(42) và sau đó đã cố gắng sử dụng giá trị trả lại.

Như vậy, trong khi trình biên dịch có thể cung cấp cảnh báo chẩn đoán thiếu báo cáo trả về thì không thể làm như vậy mà không có kết quả dương hoặc sai âm tính giả. Đây là hậu quả của việc không thể giải quyết được vấn đề ngăn chặn.

+2

+1 Rất tốt để làm rõ điều này. Lỗi thời gian biên dịch của Java và Ada về số lần trả về bị thiếu được phát hành bất cứ khi nào phát hiện có _exists_ một số đường dẫn thông qua hàm có thể thoát mà không có câu lệnh trả về, không phải là đường dẫn như vậy sẽ được thực hiện một cách chắc chắn. –

0

Không có biến cục bộ nào, do đó giá trị trên đầu ngăn xếp ở cuối hàm sẽ là tham số c. Giá trị ở trên cùng của ngăn xếp khi thoát, là giá trị trả lại. Vì vậy, bất cứ điều gì c giữ, đó là giá trị trả lại.

+0

Cảm ơn bạn hướng dẫn dễ dàng – harrison

0

Bạn nên nhớ rằng mã như vậy có thể bị lỗi tùy thuộc vào trình biên dịch. Ví dụ, clang tạo ra lệnh ud2 ở cuối hàm như vậy và ứng dụng của bạn sẽ bị lỗi khi chạy.

0

Tôi đã thử một programm nhỏ:

#include <stdio.h> 
int f1() { 
} 
int main() { 
    printf("TEST: <%d>\n", f1()); 
    printf("TEST: <%d>\n", f1()); 
    printf("TEST: <%d>\n", f1()); 
    printf("TEST: <%d>\n", f1()); 
    printf("TEST: <%d>\n", f1()); 
} 

Kết quả:

TEST: < 1>

TEST: < 10>

TEST: < 11>

TEST: < 11>

TEST: < 11>

Tôi đã sử dụng trình biên dịch mingw32-gcc, vì vậy có thể có diferences.

Bạn chỉ có thể chơi xung quanh và thử ví dụ: một hàm char. Khi bạn không sử dụng giá trị kết quả, nó sẽ làm việc tốt.

#include <stdio.h> 
char f1() { 
} 
int main() { 
    f1(); 
} 

Nhưng tôi sẽ khuyên bạn nên đặt chức năng trống hoặc trả lại một số giá trị.

Chức năng của bạn dường như cần một sự trở lại:

char toUpper(char c) 
{ 
    if(c>='a'&&c<='z') 
     c = c - 32; 
    return c; 
} 
Các vấn đề liên quan