2012-01-11 34 views
20

Tôi thấy hành vi mà tôi không mong đợi khi biên dịch mã này với các mức tối ưu hóa khác nhau trong gcc.Mức tối ưu hóa trong hành vi thay đổi chương trình ccc gcc

Kiểm tra chức năng phải điền số nguyên không dấu 64 bit với số nguyên, dịch chuyển bit shift_size sang trái và trả lại 32 bit thấp dưới dạng số nguyên không dấu 32 bit.

Khi tôi biên dịch với -O0 tôi nhận được kết quả mong đợi.

Khi tôi biên dịch với -O2 Tôi không, nếu tôi cố gắng dịch chuyển 32 bit trở lên. Trong thực tế, tôi nhận được chính xác kết quả tôi mong đợi nếu tôi đang chuyển số nguyên 32 bit bằng các thay đổi lớn hơn hoặc bằng chiều rộng bit trên x86, đó là một sự thay đổi chỉ sử dụng 5 bit thấp của kích thước thay đổi .

Nhưng tôi đang chuyển số 64 bit, vì vậy, thay đổi < 64 phải hợp pháp?

Tôi cho rằng đó là lỗi trong sự hiểu biết của tôi chứ không phải trong trình biên dịch, nhưng tôi đã không thể đoán được.

máy của tôi: gcc (Ubuntu/Linaro 4.4.4-14ubuntu5) 4.4.5 i686-linux-gnu

#include <stdint.h> 
#include <stdio.h> 
#include <inttypes.h> 

uint32_t test(unsigned int shift_size) { 
    uint64_t res = 0; 
    res = ~res; 
    res = res << shift_size; //Shift size < uint64_t width so this should work 
    return res; //Implicit cast to uint32_t 
} 

int main(int argc, char *argv[]) 
{ 
    int dst; 
    sscanf(argv[1], "%d", &dst); //Get arg from outside so optimizer doesn't eat everything 
    printf("%" PRIu32 "l\n", test(dst)); 
    return 0; 
} 

Cách sử dụng:

$ gcc -Wall -O0 test.c 
$ ./a.out 32 
0l 
$ gcc -Wall -O2 test.c 
$ ./a.out 32 
4294967295l 

gcc -S -Wall -O0 test.c

gcc -S -Wall -O2 test.c

+1

Bạn cũng có thể đăng các phần liên quan của mã lắp ráp do trình biên dịch của bạn tạo ra không? ('-S' cho gcc) –

+0

FWIW nó cho kết quả chính xác (' 0l') cho tôi trên tất cả các mức tối ưu từ '-O0' đến' -O3' bằng gcc 4.2.1, vì vậy tôi nghi ngờ bạn * có thể * có tìm thấy lỗi gcc. –

+0

hm, mã hoạt động tốt ở cả hai mức tối ưu cho tôi ... (gcc version 4.5.0 20100604, openSUSE 11.3 (x86_64)) – Bort

Trả lời

1

Dường như lỗi. My đoán là trình biên dịch đã gấp hai dòng cuối cùng từ:

res = res << shift_size 
return (uint32_t)res; 

thành:

return ((uint32_t)res) << shift_size; 

Sau đó bây giờ cũng xác định cho 32 hoặc lớn hơn.

3

Dường như nó có thể là lỗi trình biên dịch cụ thể 32 bit đối với tôi. Với mã từ câu hỏi và gcc 4.2.1 tôi có thể tạo lại lỗi này miễn là tôi biên dịch với gcc -m32 -O2 ....

Tuy nhiên, nếu tôi thêm một debug printf:

uint32_t test(unsigned int shift_size) { 
    uint64_t res = 0; 
    res = ~res; 
    res = res << shift_size; //Shift size < uint64_t width so this should work 
    printf("res = %llx\n", res); 
    return res; //Implicit cast to uint32_t 
} 

thì vấn đề đi xa.

Bước tiếp theo là xem mã được tạo để thử và xác định/xác nhận lỗi.

+0

Như một giải pháp tạm thời, có lẽ người ta có thể in thành '/ dev/null':' FILE * null; null = fopen ("/ dev/null", "w"); fprintf (null, "res =% llx \ n", res); fclose (null); ' –

+1

Như một giải pháp tạm thời, làm cho' res' 'volatile' hoạt động tốt và không yêu cầu thao tác tệp. – Fanael

5

"%u" (hoặc "%lu") và uint32_t không nhất thiết phải tương thích. Hãy thử

#include <inttypes.h> 

    //printf("%ul\n", test(dst)); 
    printf("%" PRIu32 "l\n", test(dst)); 

In một giá trị uint32_t với một "%u" (hoặc "%lu") specifier có thể gọi hành vi undefined.

+0

Cuộc gọi tốt, uint32_t có thể không được bỏ dấu int. – dreamlax

+0

@dreamlax: Tôi nghĩ rằng điểm OP được sử dụng "% ul" do nhầm lẫn thay vì "% lu", kết thúc bằng việc lấy printf là "% u". Sử dụng PRIu32 sẽ loại bỏ khả năng sử dụng một trình chỉ định không chính xác. – tinman

+1

Cùng một điểm áp dụng cho cả ''% u ''và' "% lu" '. – pmg

-2

Tôi không nhớ gì C99 nói, nhưng có vẻ như trong gcc, uint32_t chứa ít nhất 32 bit, và có thể chứa nhiều hơn, vì vậy khi bạn tối ưu hóa nó, nó sử dụng 64bit var, đó là nhanh hơn trong Máy 64 bit.

+2

Không, uint32_t, nếu có, chính xác là 32 bit. (uint_fast32_t hoặc uint_least32_t có thể lớn hơn, nhưng không phải uint32_t) – nos

+0

uint64_t không có trong mọi trường hợp nhanh hơn uint32_t: nhanh hơn để lập chỉ mục trong mảng iff bạn có không gian địa chỉ 64 bit trên máy intel/amd64, vì chỉ mục 16 bit và 32 bit phải được chuyển đổi trong khi 8bit và 64bit là các offset hợp pháp trong ngôn ngữ assembly đó. nhưng nhân và phân chia là chậm hơn iff bạn sử dụng kích thước bit lớn hơn. – comonad

4

Tôi có thể repro điều này. Dưới đây là các bit có liên quan của mã được tạo với -O2:

movl $-1, %eax 
    movl $-1, %edx 
    sall %cl, %eax 
    xorl %edx, %edx 
    testb   $32, %cl 
    cmovne  %eax, %edx 
    cmovne  %edx, %eax ; This appears to be the instruction in error. 
          ; It looks as though gcc thought that %edx might still 
          ; be zero at this point, because if the shift count is 
          ; >= 32 then %eax should be zero after this. 

và đây là bit tương đương với -O0:

movl -16(%ebp), %eax 
    movl -12(%ebp), %edx 
    shldl %cl,%eax, %edx 
    sall %cl, %eax 
    testb   $32, %cl 
    je      L3 
    movl    %eax, %edx 
    xorl    %eax, %eax   ; correctly zeros %eax if shift count >= 32 
L3: 
    movl %eax, -16(%ebp) 
    movl %edx, -12(%ebp) 

Compiler là:

i686-apple-darwin11-gcc-4.2.1 (GCC) 4.2.1 (Apple Inc. build 5666) (dot 3) 

Cám ơn đăng sản lượng gcc -S của bạn. Tôi đã có một cái nhìn và trong khi nó hơi khác nhau, phần quan trọng có lỗi giống như những gì tôi nhìn thấy trên máy tính của tôi.

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