Đoạn mã sau (C99 và mới hơn) muốn tính một hình vuông, bị giới hạn ở cùng một số bit như loại chiều rộng cố định ban đầu.Làm thế nào để buộc số học chưa ký trên các loại chiều rộng cố định?
#include <stdint.h>
uint8_t sqr8(uint8_t x) { return x*x; }
uint16_t sqr16(uint16_t x) { return x*x; }
uint32_t sqr32(uint32_t x) { return x*x; }
uint64_t sqr64(uint64_t x) { return x*x; }
Vấn đề là: tùy thuộc vào kích thước int, một số các phép nhân có thể được thực hiện trên đối số thăng (đã ký) int, với kết quả tràn một (đã ký) int, kết quả như vậy, không xác định như xa như tiêu chuẩn là có liên quan ; và kết quả sai lầm có thể tưởng tượng được, đặc biệt là trên các máy (ngày càng hiếm) không sử dụng two's complement.
Nếu int
là 32-bit (resp. 16-bit, 64-bit, 80 hoặc 128-bit), xảy ra cho sqr16
(resp. sqr8
, sqr32
, sqr64
) khi x
là 0xFFFFF
(resp. 0xFF
, 0xFFFFFFFF
, 0xFFFFFFFFFFFFFFFF
). Cả 4 chức năng đều không thể di chuyển theo C99 !!
C11 hoặc mới hơn, hoặc một số ấn bản C++, khắc phục tình huống không may đó?
A, giải pháp làm việc đơn giản là:
#include <stdint.h>
uint8_t sqr8(uint8_t x) { return 1u*x*x; }
uint16_t sqr16(uint16_t x) { return 1u*x*x; }
uint32_t sqr32(uint32_t x) { return 1u*x*x; }
uint64_t sqr64(uint64_t x) { return 1u*x*x; }
Đây là tiêu chuẩn tuân thủ QTI vì 1u
không thăng int
và vẫn unsigned; do đó phép nhân bên trái, sau đó là phép nhân bên phải, được thực hiện dưới dạng unsigned, do đó được xác định rõ để mang lại kết quả chính xác trong số lượng bit có thứ tự thấp cần thiết; tương tự cho các diễn viên tiềm ẩn cuối cùng với chiều rộng kết quả.
Cập nhật: Như đề nghị trong comment by Marc Glisse, tôi đã cố gắng biến thể này với tám biên dịch (ba phiên bản của GCC cho x86 bắt đầu với 3.1, MS C/C++ 19.00, Keil ARM trình biên dịch 5, hai trình biên dịch vũ trụ cho các biến thể ST7, Microchip MCC18). Tất cả chúng đều tạo ra cùng một mã như bản gốc (với các tối ưu tôi sử dụng trong chế độ phát hành cho các dự án thực tế). Tuy nhiên, các trình biên dịch có thể tạo ra mã tồi tệ hơn so với bản gốc; và tôi có một số trình biên dịch nhúng khác của tôi để thử, bao gồm một số trình biên dịch 68K và PowerPC.
Chúng tôi có những tùy chọn nào khác, tạo sự cân bằng hợp lý giữa khả năng hoạt động tốt hơn, dễ đọc và đơn giản hơn?
Cast rộng hơn cho phép nhân, sau đó đúc trở lại kiểu hẹp 'uint8_t sqr8 (uint8_t x) {return (uint8_t) ((uint16_t) x * (uint16_t) x); } ' – Toby
Bạn nói đúng về quan sát chung: các bí danh loại không tách rời có kích thước cố định không phù hợp cho các phép tính số học với hành vi mô-đun. Sử dụng 'unsigned long' hoặc một cái gì đó như thế. Điều quan trọng là bạn cần phải kiểm soát xếp hạng chuyển đổi của loại và các bí danh có kích thước chứa thông tin xếp hạng chuyển đổi * không *. Thậm chí 'uintmax_t' có thể là một bí danh cho' unsigned char'. –
@Toby: sự hiểu biết của tôi là ngay cả với các phôi, các đối số sẽ * vẫn * được thăng thành 'int'. – Bathsheba