2010-06-10 2 views
14

Tôi đang lập trình C trên cửa sổ Cygwin. Sau khi đã thực hiện một chút lập trình C và cảm thấy thoải mái với ngôn ngữ, tôi muốn xem xét dưới mui xe và xem những gì trình biên dịch đang làm cho mã mà tôi viết.Trường hợp chuyển đổi mã mức lắp ráp

Vì vậy, tôi đã viết xuống một khối mã có chứa báo cáo trường hợp chuyển đổi và chuyển đổi chúng thành lắp ráp sử dụng:

gcc -S foo.c 

Đây là nguồn C:

switch(i) 
{ 
    case 1: 
    { 
     printf("Case 1\n"); 
     break; 
    } 
    case 2: 
    {   printf("Case 2\n"); 
     break; 
    } 
    case 3: 
    { 
     printf("Case 3\n"); 
     break; 
    } 
    case 4: 
    { 
     printf("Case 4\n"); 
     break; 
    } 
    case 5: 
    { 
     printf("Case 5\n"); 
     break; 
    } 
    case 6: 
    { 
     printf("Case 6\n"); 
     break; 
    } 
    case 7: 
    { 
     printf("Case 7\n"); 
     break; 
    } 
    case 8: 
    { 
     printf("Case 8\n"); 
     break; 
    } 
    case 9: 
    { 
     printf("Case 9\n"); 
     break; 
    } 
    case 10: 
    { 
     printf("Case 10\n"); 
     break; 
    } 
    default: 
    { 
     printf("Nothing\n"); 
     break; 
    } 
} 

Bây giờ lắp ráp kết quả cho cùng là :

movl $5, -4(%ebp) 
cmpl $10, -4(%ebp) 
ja L13 
movl -4(%ebp), %eax 
sall $2, %eax 
movl L14(%eax), %eax 
jmp *%eax 
.section .rdata,"dr" 
.align 4 
L14: 
.long L13 
.long L3 
.long L4 
.long L5 
.long L6 
.long L7 
.long L8 
.long L9 
.long L10 
.long L11 
.long L12 
.text 
L3: 
movl $LC0, (%esp) 
call _printf 
jmp L2 
L4: 
movl $LC1, (%esp) 
call _printf 
jmp L2 
L5: 
movl $LC2, (%esp) 
call _printf 
jmp L2 
L6: 
movl $LC3, (%esp) 
call _printf 
jmp L2 
L7: 
movl $LC4, (%esp) 
call _printf 
jmp L2 
L8: 
movl $LC5, (%esp) 
call _printf 
jmp L2 
L9: 
movl $LC6, (%esp) 
call _printf 
jmp L2 
L10: 
movl $LC7, (%esp) 
call _printf 
jmp L2 
L11: 
movl $LC8, (%esp) 
call _printf 
jmp L2 
L12: 
movl $LC9, (%esp) 
call _printf 
jmp L2 
L13: 
movl $LC10, (%esp) 
call _printf 
L2: 

Bây giờ, trong hội đồng, mã đầu tiên kiểm tra trường hợp cuối cùng (ví dụ: trường hợp 10) đầu tiên. Điều này rất lạ. Và sau đó nó được sao chép 'i' vào 'eax' và làm những thứ vượt ra ngoài tôi.

Tôi đã nghe nói rằng trình biên dịch thực hiện một số bước nhảy cho switch..case. Có phải mã này đang làm gì không? Hoặc nó đang làm gì và tại sao? Bởi vì trong trường hợp số ít trường hợp, mã này khá giống với mã được tạo cho nếu ... khác bậc thang, nhưng khi số lượng các trường hợp tăng lên, việc thực hiện trông lạ thường này được nhìn thấy.

Xin cảm ơn trước.

+0

Thật không may là nó không tối ưu hóa việc tìm kiếm bảng con trỏ chuỗi và 'cuộc gọi _printf'. Không có gcc/clang/icc nào làm điều đó ngay cả ở '-O3'. https://godbolt.org/g/JrSwU3 (Tuy nhiên, chúng tối ưu hóa 'printf' thành' puts', và tối ưu hóa đuôi gọi đến 'jmp' thay vì' call'/'ret') –

Trả lời

21

Đầu tiên mã sẽ so sánh từ i đến 10 và nhảy đến trường hợp mặc định khi giá trị lớn hơn 10 (cmpl $10, -4(%ebp) theo sau là ja L13).

Đoạn mã tiếp theo đang dịch chuyển đầu vào sang trái bằng hai (sall $2, %eax) giống như bội số bốn tạo ra một bù vào bảng nhảy (vì mỗi mục trong bảng dài 4 byte)

Sau đó, nó tải địa chỉ từ bảng nhảy (movl L14(%eax), %eax) và nhảy tới địa chỉ đó (jmp *%eax).

Bảng nhảy chỉ đơn giản là một danh sách các địa chỉ (đại diện trong mã lắp ráp bởi nhãn):

L14: 
.long L13 
.long L3 
.long L4 
... 

Một điều cần chú ý đó là L13 đại diện cho trường hợp mặc định. Đây là mục nhập đầu tiên trong bảng nhảy (khi tôi là 0) và được xử lý đặc biệt ngay từ đầu (khi tôi> 10).

+0

Tôi thấy ... đây là thông tin. Nhưng sau đó tại sao trình biên dịch không tạo ra một bảng nhảy trong trường hợp có ít trường hợp hơn (như 2 hoặc 3)? – puffadder

+0

@puffadder: các trình biên dịch hiện đại nhất sử dụng các chẩn đoán để xác định khi nào hiệu quả hơn khi sử dụng các nhánh so với một bảng nhảy. Ví dụ. nếu mức độ của bạn là 1, 100 và 1000 bạn có thể mong đợi các nhánh được sử dụng. –

+0

Tôi không hiểu tại sao mỗi bước nhảy vào trường hợp mặc định thay vì hoàn toàn chuyển đổi. Ai đó có thể giải thích điều này? – mharris7190

2

Đối với [1..10] trình biên dịch sẽ tạo bảng để không cần so sánh giá trị để đi đâu đó, nó trực tiếp thực hiện: goto table[i]. Bằng cách đó nó nhanh hơn.

Nhưng trong trường hợp i > 10 nó chuyển sang câu lệnh mặc định của bạn. Nó phải kiểm tra đầu tiên trước khi nhảy khác, chương trình sẽ sụp đổ thảm hại.

Nếu bạn có giá trị thưa thớt (như, 23, 9233, 91238 và không phải 1, 2, 3 ...), trình biên dịch sẽ không tạo bảng như vậy và so sánh từng giá trị.

0

Vâng, eax đầu tiên được tính bằng giá trị chuyển đổi (sall thay đổi như phép nhân) để có được những địa chỉ từ bảng nhảy (sau nhãn L14:)

jmp *%eax là một bước nhảy gần nhãn của trường hợp của bạn. (jmp gần eax)

Mã theo các nhãn khác chỉ in và bỏ qua các trường hợp khác.

3

Có nó là một bảng nhảy. Việc kiểm tra đầu tiên là để kiểm tra xem giá trị có trong các trường hợp và nhảy đến mặc định nếu nó không phải là. Đừng quên rằng trong bảng như vậy, nếu% eax là 0, L14 (% eax) trỏ đến phần tử đầu tiên của bảng (L13). Vì vậy, trong bảng, case 10: được lập chỉ mục với 9, không phải là 10.

Cách chuyển đổi có thể được thực hiện tùy thuộc vào giá trị bạn có trong case; trong trường hợp này chúng nằm trong "chuỗi", do đó, bảng nhảy đơn giản là có thể.