2009-04-26 25 views
6

Tôi có đoạn code C sau:Ngôn ngữ C: Giá trị #DEFINEd làm lộn xộn phép nhân 8 bit. Tại sao?

#define PRR_SCALE 255 
... 
uint8_t a = 3; 
uint8_t b = 4; 
uint8_t prr; 
prr = (PRR_SCALE * a)/b; 
printf("prr: %u\n", prr); 

Nếu tôi biên dịch này (sử dụng một trình biên dịch nền tảng MSP430, cho một nhúng hệ điều hành nhỏ gọi là contiki) kết quả là 0 trong khi tôi mong đợi 191. (uint8_t là typedef' ed như một char unsigned)

Nếu tôi thay đổi nó để:

uint8_t a = 3; 
uint8_t b = 4; 
uint8_t c = 255; 
uint8_t prr; 
prr = (c * a)/b; 
printf("prr: %u\n", prr); 

nó hoạt động một cách chính xác và in 191.

01.235.

Biên dịch một phiên bản đơn giản của 'bình thường' này bằng cách sử dụng gcc trên một hộp Ubuntu in giá trị chính xác trong cả hai trường hợp.

Tôi không chắc chắn lý do tại sao điều này xảy ra. Tôi có thể phá vỡ nó bằng cách gán giá trị DEFINEd cho biến trước, nhưng tôi không muốn làm điều đó.

Có ai biết tại sao điều này không? Có lẽ với một liên kết đến một số thông tin thêm về điều này?

+0

tôi chắc chắn sẽ mong đợi cả hai để in 191. trong trường hợp thứ hai, đầu tiên c và một được thúc đẩy int độc lập và do đó nhân của họ không thể tràn. Điều tương tự cũng xảy ra trong trường hợp đầu tiên (mặc dù ở đó, PRR_SCALE đã là int - nhưng điều đó sẽ không thay đổi quảng cáo của a thành int). gcc của bạn trên hộp của bạn hoạt động chính xác tốt. –

+0

kiểm tra xem bạn đã bao gồm tiêu đề stdio.h chưa. tôi biết rằng một trình biên dịch cho msp430 không cho phép khai báo hàm tiềm ẩn xảy ra: nếu đó là trường hợp, lệnh gọi printf sẽ gây ra hành vi không xác định và kết quả "0" sẽ được giải thích qua đó. chỉ hai xu của tôi. không nghĩ rằng đây là giá trị một câu trả lời :) –

+0

@ litb: stdio.h được bao gồm. – Rabarberski

Trả lời

10

Câu trả lời ngắn gọn: trình biên dịch của bạn bị lỗi. (Không có vấn đề với tràn, như những người khác đề nghị.)

Trong cả hai trường hợp, số học được thực hiện trong int, được đảm bảo dài ít nhất 16 bit. Trong đoạn mã trước đó là bởi vì 255 là một số int, sau đó là vì integral promotion.

Như bạn đã lưu ý, gcc xử lý chính xác điều này.

+0

"số học được thực hiện bằng int, được đảm bảo dài ít nhất 16 bit" - Bạn có thể cung cấp nguồn trên đó không? Tôi đặt câu hỏi nó có giá trị ... – strager

+1

"số học được thực hiện trong int": kết quả của việc thúc đẩy tích phân. "[int] được đảm bảo dài ít nhất 16 bit": được bảo đảm gián tiếp, INT_MAX phải có ít nhất 32767 và INT_MIN ở mức cao nhất là 32767 (xem phần giới hạn.h trong tiêu chuẩn). – avakar

+0

+1, đó là những gì tôi mong đợi sau khi gỡ lỗi một chút. Bạn có thể cung cấp liên kết chỉ định cách giá trị được chuyển đổi trở lại không? Hay nó chỉ là số học mô-đun? – JaredPar

2

255 đang được xử lý dưới dạng số nguyên nguyên và làm cho toàn bộ biểu thức được dựa trên int chứ không phải là ký tự dựa trên unsigned char. Trường hợp thứ hai buộc loại phải chính xác. Hãy thử thay đổi #define của bạn như sau:

#define PRR_SCALE ((uint8_t) 255) 
+0

Không hoạt động, tôi đã thử điều đó (tốt, tôi đã làm diễn viên trực tiếp trong dòng tính toán. Tôi đã thử lại như bạn đã đề xuất. Không có sự khác biệt). – Rabarberski

+0

Tôi cả hai ví dụ mã, số học được thực hiện trong int.Điều này là do các chương trình khuyến mãi số nguyên, xem http://www.open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf, Mục 6.3.1.1, đoạn 2. –

1

Tôi không chắc chắn lý do tại sao xác định không làm việc, nhưng bạn có thể chạy vào hiệu ứng Rollover với uint8_t biến. 255 là giá trị tối đa cho uint8_t (2^8 - 1), vì vậy nếu bạn nhân số đó với 3, bạn sẽ bị ràng buộc để chạy vào một số vấn đề di chuột qua tinh tế.

Trình biên dịch có thể tối ưu hóa mã của bạn và tính trước kết quả biểu thức toán học của bạn và đẩy kết quả bằng prr (vì nó phù hợp, mặc dù giá trị trung gian không phù hợp).

Kiểm tra những gì sẽ xảy ra nếu bạn chia tay biểu hiện của bạn như thế này (điều này sẽ không cư xử như những gì bạn muốn):

prr = c * a; // rollover! 
prr = prr/b; 

Bạn có thể cần phải chỉ cần sử dụng một kiểu dữ liệu lớn hơn.

+1

Về mặt kỹ thuật c * a không tràn ra. Tiêu chuẩn C chỉ định rằng một tràn không thể xảy ra đối với các loại số nguyên không dấu. – JaredPar

+0

Tuyệt, điều đó có ý nghĩa. Tôi đoán "rollover" sẽ là một thuật ngữ tốt hơn. –

0

Một khác biệt mà tôi có thể nghĩ đến trong trường hợp-1 là,

Các giá trị văn chương PRR_SCALE có thể đi vào ROM hoặc mã vùng. Và có thể có một số khác biệt trong MUL opecode cho biết,

case-1: [register], [rom] 
case -2: [register], [register] 

Nó hoàn toàn không có ý nghĩa gì cả.

2

Nếu trình biên dịch được đề cập là mspgcc, nó sẽ đưa ra danh sách trình biên dịch của chương trình đã biên dịch cùng với tệp nhị phân/hex. Các trình biên dịch khác có thể yêu cầu các cờ biên dịch bổ sung để làm như vậy. Hoặc thậm chí có thể một trình tách rời riêng biệt chạy trên nhị phân.

Đây là nơi để tìm giải thích. Do tối ưu hóa trình biên dịch, mã thực tế được trình bày cho bộ xử lý có thể không có nhiều điểm tương đồng với mã C ban đầu (nhưng thường có cùng một công việc).

Bước qua một vài hướng dẫn lắp ráp đại diện cho mã bị lỗi sẽ tiết lộ nguyên nhân của sự cố.

Tôi đoán là trình biên dịch bằng cách nào đó tối ưu hóa toàn bộ phép tính sice hằng số xác định là một phần được biết tại thời gian biên dịch. 255 * x có thể được tối ưu hóa thành x < < 8-x (nhanh hơn và nhỏ hơn) Có thể đã xảy ra sự cố với mã trình tối ưu hóa.

Tôi đã dành thời gian để biên dịch cả hai phiên bản trên hệ thống của mình. Với tối ưu hóa hoạt động, sản xuất mspgcc đoạn mã sau:

#define PRR_SCALE 255 
uint8_t a = 3; 
uint8_t b = 4; 
uint8_t prr; 
prr = (PRR_SCALE * a)/b; 
    40ce: 3c 40 fd ff  mov #-3, r12 ;#0xfffd 
    40d2: 2a 42   mov #4, r10 ;r2 As==10 
    40d4: b0 12 fa 6f  call __divmodhi4 ;#0x6ffa 
    40d8: 0f 4c   mov r12, r15 ; 
printf("prr: %u\n", prr); 
    40da: 7f f3   and.b #-1, r15 ;r3 As==11 
    40dc: 0f 12   push r15  ; 
    40de: 30 12 c0 40  push #16576  ;#0x40c0 
    40e2: b0 12 9c 67  call printf  ;#0x679c 
    40e6: 21 52   add #4, r1 ;r2 As==10 

Như chúng ta có thể thấy, trình biên dịch trực tiếp tính toán kết quả của 255 * 3 đến -3 (0xfffd). Và đây là vấn đề. Bằng cách nào đó 255 được hiểu là -1 ký 8-bit thay vì 255 unsigned 16 bit. Hoặc nó được phân tích cú pháp thành 8 bit trước và sau đó mở rộng ký hiệu thành 16 bit. hay bất cứ cái gì.

Thảo luận về chủ đề này đã được bắt đầu tại danh sách gửi thư mspgcc rồi.

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