Có bất kỳ tiện ích mở rộng C đáng chú ý nào bao gồm các loại số nguyên có hành vi độc lập với kích thước từ của máy không


12

Một đặc điểm thú vị của C so với một số ngôn ngữ khác là nhiều loại dữ liệu của nó dựa trên kích thước từ của kiến ​​trúc đích, thay vì được chỉ định theo thuật ngữ tuyệt đối. Mặc dù điều này cho phép ngôn ngữ được sử dụng để viết mã trên các máy có thể gặp khó khăn với một số loại nhất định, nhưng điều này gây khó khăn cho việc thiết kế mã sẽ chạy ổn định trên các kiến ​​trúc khác nhau. Hãy xem xét mã:

uint16_t ffff16 = 0xFFFF;
int64_t who_knows = ffff16 * ffff16;

Trên một kiến ​​trúc có int16 bit (vẫn đúng với nhiều bộ vi điều khiển nhỏ), mã này sẽ gán giá trị 1 bằng cách sử dụng hành vi được xác định rõ. Trên các máy có int64 bit, nó sẽ gán giá trị 4294836225, một lần nữa sử dụng hành vi được xác định rõ. Trên các máy có int32 bit, nó có thể sẽ gán giá trị -131071 (Tôi không biết đó sẽ là Hành vi được Xác định theo Thực hiện hay Không xác định). Mặc dù mã không sử dụng gì ngoại trừ loại được coi là loại "có kích thước cố định", tiêu chuẩn sẽ yêu cầu hai loại trình biên dịch khác nhau được sử dụng ngày nay sẽ mang lại hai kết quả khác nhau và nhiều trình biên dịch phổ biến hiện nay sẽ mang lại kết quả thứ ba.

Ví dụ cụ thể này hơi khó hiểu, trong đó tôi không mong đợi trong mã trong thế giới thực sẽ gán trực tiếp sản phẩm của hai giá trị 16 bit cho giá trị 64 bit, nhưng nó được chọn làm ví dụ ngắn gọn để hiển thị ba số nguyên chương trình khuyến mãi có thể tương tác với các loại không dấu được cho là có kích thước cố định. Có một số tình huống trong thế giới thực, trong đó toán học cần phải được thực hiện theo các quy tắc của số học toán học số nguyên, những trường hợp khác cần phải được thực hiện theo quy tắc của số học mô-đun và một số nơi thực sự không ' vấn đề Rất nhiều mã trong thế giới thực cho những thứ như tổng kiểm tra dựa trên uint32_tgói số học 2³² và khi có thể thực hiện tùy ýuint16_t số học và nhận được kết quả tối thiểu, được xác định là mod chính xác 65536 (trái ngược với việc kích hoạt Hành vi không xác định).

Mặc dù tình huống này rõ ràng có vẻ không mong muốn (và sẽ trở nên nhiều hơn khi xử lý 64 bit trở thành chuẩn mực cho nhiều mục đích), ủy ban tiêu chuẩn C từ những gì tôi quan sát thấy thích giới thiệu các tính năng ngôn ngữ đã được sử dụng trong một số sản phẩm đáng chú ý môi trường, thay vì phát minh ra chúng "từ đầu". Có bất kỳ tiện ích mở rộng đáng chú ý nào đối với ngôn ngữ C sẽ cho phép mã xác định không chỉ cách thức một loại sẽ được lưu trữ mà còn cách nó hoạt động trong các tình huống liên quan đến các chương trình khuyến mãi có thể không? Tôi có thể thấy ít nhất ba cách một phần mở rộng trình biên dịch có thể giải quyết các vấn đề như vậy:

  1. Bằng cách thêm một lệnh sẽ hướng dẫn trình biên dịch buộc các loại số nguyên "cơ bản" nhất định phải có kích thước nhất định.

  2. Bằng cách thêm một lệnh sẽ hướng dẫn trình biên dịch đánh giá các kịch bản quảng cáo khác nhau như thể các loại máy có kích thước cụ thể, bất kể kích thước thực tế của các loại trên kiến ​​trúc đích.

  3. Bằng cách cho phép các phương tiện khai báo các loại có các đặc điểm cụ thể (ví dụ: tuyên bố rằng một loại sẽ hoạt động như một vòng đại số bao bọc mod-65536, bất kể kích thước từ cơ bản và không được chuyển đổi hoàn toàn sang các loại khác; thêm một wrap32vào intsẽ mang lại một kết quả của loại wrap32bất kể intlớn hơn 16 bit, trong khi thêm wrap32trực tiếp vào một wrap16nên là bất hợp pháp (vì không thể chuyển đổi sang loại khác).

Sở thích riêng của tôi sẽ là lựa chọn thay thế thứ ba, vì nó sẽ cho phép ngay cả các máy có kích thước từ bất thường hoạt động với nhiều mã dự kiến ​​các biến sẽ "bao bọc" như với kích cỡ có hai kích thước; trình biên dịch có thể phải thêm các hướng dẫn che mặt bit để làm cho kiểu hoạt động phù hợp, nhưng nếu mã cần một kiểu bao bọc mod 65536, tốt hơn là để trình biên dịch tạo mặt nạ như vậy trên các máy cần nó hơn là làm lộn xộn mã nguồn với nó hoặc đơn giản là có mã như vậy bằng cách không sử dụng được trên các máy mà việc che dấu như vậy là cần thiết. Tuy nhiên, tôi tò mò liệu có bất kỳ tiện ích mở rộng phổ biến nào sẽ đạt được hành vi di động thông qua bất kỳ phương tiện nào ở trên không, hoặc thông qua một số phương tiện mà tôi chưa từng nghĩ tới.

Để làm rõ những gì tôi đang tìm kiếm, có một vài điều; đáng chú ý nhất:

  1. Mặc dù có nhiều cách mà mã có thể được viết để đảm bảo ngữ nghĩa mong muốn (ví dụ: xác định các macro để thực hiện phép toán trên các toán hạng không dấu có kích thước cụ thể để mang lại kết quả rõ ràng là kết thúc hoặc không) hoặc ít nhất là ngăn chặn không mong muốn ngữ nghĩa (ví dụ như có điều kiện-định nghĩa một kiểu wrap32_tuint32_ttrên các trình biên dịch, nơi một uint32_tsẽ không được thăng tiến, và con số đó sẽ tốt hơn cho mã đòi hỏi wrap32_tthất bại biên soạn trên máy nơi kiểu đó sẽ được thăng tiến hơn để có nó chạy và mang lại hành vi giả mạo), nếu có bất kỳ cách viết mã nào có thể chơi thuận lợi nhất với các phần mở rộng ngôn ngữ trong tương lai, sử dụng cách đó sẽ tốt hơn là nghĩ ra cách tiếp cận của riêng tôi.

  2. Tôi có một số ý tưởng khá vững chắc về cách ngôn ngữ có thể được mở rộng để giải quyết nhiều vấn đề kích thước nguyên, cho phép mã mang lại ngữ nghĩa giống hệt nhau trên các máy có kích thước từ khác nhau, nhưng trước khi tôi dành thời gian đáng kể để viết chúng, tôi muốn để biết những nỗ lực theo hướng đó đã được thực hiện.

Tôi không muốn bị coi là chê bai Ủy ban Tiêu chuẩn C hoặc công việc họ đã sản xuất; Tuy nhiên, tôi hy vọng rằng trong một vài năm, sẽ cần thiết để làm cho mã hoạt động chính xác trên các máy có loại quảng cáo "tự nhiên" sẽ có 32 bit, cũng như những nơi có 64 bit. Tôi nghĩ rằng với một số phần mở rộng khiêm tốn cho ngôn ngữ (khiêm tốn hơn nhiều thay đổi khác giữa C99 nnd C14), không chỉ có thể cung cấp một cách hiệu quả sử dụng kiến ​​trúc 64 bit, mà trong giá cả cũng tạo điều kiện cho việc tương tác với các máy "có kích thước từ bất thường" mà tiêu chuẩn đã từng sử dụng để hỗ trợ [ví dụ: làm cho các máy có mã 12 bit có charthể chạy mã mong đợiuint32_tđể quấn mod 2³²]. Tùy thuộc vào hướng mà các tiện ích mở rộng trong tương lai thực hiện, tôi cũng hy vọng có thể xác định các macro sẽ cho phép mã được viết ngày hôm nay có thể sử dụng được trên các trình biên dịch ngày nay trong đó các kiểu số nguyên mặc định hoạt động như "mong đợi", nhưng cũng có thể sử dụng trên các trình biên dịch trong tương lai các loại sẽ được mặc định hành xử khác nhau, nhưng nơi có thể cung cấp các hành vi cần thiết.


4
@RobertHarvey Bạn có chắc không? Theo tôi hiểu khuyến mãi số nguyên , nếu intlớn hơn uint16_t, các toán hạng của phép nhân sẽ được thăng cấp lên intvà phép nhân sẽ được thực hiện dưới intdạng phép nhân và intgiá trị kết quả sẽ được chuyển đổi thành int64_tkhởi tạo who_knows.

3
@RobertHarvey thế nào? Trong mã của OP, không có đề cập intnào, nhưng nó vẫn lẻn vào. (Một lần nữa giả sử hiểu biết của tôi về tiêu chuẩn C là chính xác.)

2
@RobertHarvey Chắc chắn nghe có vẻ tệ, nhưng trừ khi bạn có thể chỉ ra một cách như vậy, bạn sẽ không đóng góp bất cứ điều gì bằng cách nói "nah bạn phải làm gì đó sai". Câu hỏi đặt ra là làm thế nào để tránh quảng cáo số nguyên, hoặc nhận được các hiệu ứng của nó!

3
@RobertHarvey: Một trong những mục tiêu lịch sử của Ủy ban Tiêu chuẩn C là tạo điều kiện cho hầu hết mọi máy có "trình biên dịch C" và có các quy tắc đủ cụ thể để trình biên dịch C phát triển độc lập cho bất kỳ máy mục tiêu cụ thể nào chủ yếu là hoán đổi cho nhau. Điều này phức tạp bởi thực tế là mọi người bắt đầu viết trình biên dịch C cho nhiều máy trước khi các tiêu chuẩn được soạn thảo và Ủy ban Tiêu chuẩn không muốn cấm trình biên dịch làm bất cứ điều gì mà mã hiện có có thể dựa vào . Một số khía cạnh khá cơ bản của tiêu chuẩn ...
supercat

3
... là vì không phải vì bất kỳ ai cố gắng xây dựng một bộ quy tắc "có ý nghĩa", mà là vì Ủy ban đã cố gắng tìm hiểu tất cả những điều mà các trình biên dịch viết độc lập đã tồn tại . Thật không may, cách tiếp cận này đã dẫn đến các tiêu chuẩn đồng thời quá mơ hồ để cho phép các lập trình viên chỉ định những gì cần phải làm, nhưng quá cụ thể để cho phép các trình biên dịch "chỉ làm điều đó".
supercat

Câu trả lời:


4

Như ý định điển hình của mã như thế này

uint16_t ffff16 = 0xFFFF;
int64_t who_knows = ffff16 * ffff16;

là để thực hiện phép nhân trong 64 bit (kích thước của biến mà kết quả được lưu trữ), cách thông thường để có kết quả đúng (độc lập với nền tảng) là tạo một trong các toán hạng để tạo phép nhân 64 bit:

uint16_t ffff16 = 0xFFFF;
int64_t i_know = (int64_t)ffff16 * ffff16;

Tôi chưa bao giờ gặp bất kỳ tiện ích mở rộng C nào làm cho quá trình này tự động.


1
Câu hỏi của tôi không phải là làm thế nào để buộc đánh giá chính xác một biểu thức số học cụ thể (tùy thuộc vào loại kết quả nào mà người ta muốn, hoặc sử dụng một toán hạng uint32_thoặc sử dụng một macro được định nghĩa là #define UMUL1616to16(x,y)((uint16_t)((uint16_t)(x)*(uint16_t)(y)))hoặc #define UMUL1616to16(x,y)((uint16_t)((uint32_t)(x)*(uint16_t)(y)))tùy thuộc vào kích thước của int) mà là có bất kỳ tiêu chuẩn mới nổi nào về cách xử lý những thứ đó một cách hữu ích thay vì xác định các macro của riêng tôi.
supercat

Tôi cũng nên đề cập rằng, đối với những thứ như băm và tính toán tổng kiểm tra, mục đích thường sẽ là lấy kết quả và cắt nó theo kích thước của toán hạng. Mục đích điển hình của một biểu thức (ushort1*ushort2) & 65535usẽ là thực hiện số học mod-65536 cho tất cả các giá trị toán hạng. Đọc lý do C89, tôi nghĩ khá rõ ràng rằng trong khi các tác giả nhận ra rằng mã đó có thể thất bại trong một số triển khai nếu kết quả vượt quá 2147483647, họ hy vọng việc triển khai như vậy sẽ ngày càng hiếm. Mã như vậy đôi khi thất bại trên gcc hiện đại, tuy nhiên.
supercat
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.