Đối với mã nhúng, tại sao tôi nên sử dụng các loại uint_t của Thay vì không được đặt tên int int?


22

Tôi đang viết một ứng dụng trong c cho STM32F105, sử dụng gcc.

Trong quá khứ (với các dự án đơn giản hơn), tôi đã luôn luôn được xác định các biến như char, int, unsigned int, và vân vân.

Tôi thấy rằng người ta thường sử dụng các loại quy định tại stdint.h, chẳng hạn như int8_t, uint8_t, uint32_t, vv Điều này đúng trong nhiều API mà tôi đang sử dụng, và cũng có trong thư viện CMSIS ARM từ ST.

Tôi tin rằng tôi hiểu tại sao chúng ta nên làm như vậy; để cho phép trình biên dịch tối ưu hóa không gian bộ nhớ tốt hơn. Tôi hy vọng có thể có thêm lý do.

Tuy nhiên, do các quy tắc quảng cáo số nguyên của c, tôi tiếp tục chạy theo các cảnh báo chuyển đổi bất cứ khi nào tôi cố gắng thêm hai giá trị, thực hiện thao tác theo bit, v.v. Cảnh báo sẽ đọc một cái gì đó như conversion to 'uint16_t' from 'int' may alter its value [-Wconversion]. Vấn đề được thảo luận ở đâyở đây .

Nó không xảy ra khi sử dụng các biến được khai báo là inthoặc unsigned int.

Để đưa ra một vài ví dụ, đưa ra điều này:

uint16_t value16;
uint8_t value8;

Tôi sẽ phải thay đổi điều này:

value16 <<= 8;
value8 += 2;

đến đây:

value16 = (uint16_t)(value16 << 8);
value8 = (uint8_t)(value8 + 2);

Thật xấu xí, nhưng tôi có thể làm điều đó nếu cần thiết. Đây là câu hỏi của tôi:

  1. Có trường hợp chuyển đổi từ không dấu sang đã ký và trở lại không dấu sẽ làm cho kết quả không chính xác?

  2. Có bất kỳ lý do lớn nào khác cho / không sử dụng các loại số nguyên stdint.h không?

Dựa trên các câu trả lời tôi nhận được, có vẻ như các loại stdint.h thường được ưa thích hơn, mặc dù c chuyển đổi uintthành intvà quay lại. Điều này dẫn đến một câu hỏi lớn hơn:

  1. Tôi có thể ngăn các cảnh báo của trình biên dịch bằng cách sử dụng typecasting (ví dụ value16 = (uint16_t)(value16 << 8);). Tôi chỉ đang che giấu vấn đề? Có cách nào tốt hơn để đi về nó?

Sử dụng chữ không dấu: tức là 8u2u.
Ngừng làm hại Monica

Cảm ơn, @OrangeDog, tôi nghĩ tôi đang hiểu lầm. Tôi đã thử cả hai value8 += 2u;value8 = value8 + 2u;, nhưng tôi nhận được cùng một cảnh báo.
bitsmack

Dù sao hãy sử dụng chúng, để tránh các cảnh báo đã ký khi bạn chưa có cảnh báo về chiều rộng :)
Ngừng làm hại Monica

Câu trả lời:


11

Trình biên dịch tuân thủ tiêu chuẩn ở intbất kỳ nơi nào từ 17 đến 32 bit có thể làm bất cứ điều gì nó muốn với mã sau đây:

uint16_t x = 46341;
uint32_t y = x*x; // temp result is signed int, which can't hold 2147488281

Việc triển khai muốn làm như vậy có thể tạo ra một chương trình một cách hợp pháp, ngoại trừ chuỗi "Fred" lặp đi lặp lại trên mỗi chân cổng bằng mọi giao thức có thể tưởng tượng được. Xác suất của một chương trình được chuyển đến một triển khai thực hiện một việc như vậy là cực kỳ thấp, nhưng về mặt lý thuyết là có thể. Nếu muốn viết mã ở trên để nó được đảm bảo không tham gia vào Hành vi không xác định, thì cần phải viết biểu thức sau là (uint32_t)x*xhoặc 1u*x*x. Trên một trình biên dịch có inttừ 17 đến 31 bit, biểu thức sau sẽ tắt các bit trên, nhưng sẽ không tham gia vào Hành vi không xác định.

Tôi nghĩ rằng các cảnh báo gcc có thể đang cố gắng đề xuất rằng mã như được viết không hoàn toàn di động 100%. Đôi khi, mã thực sự nên được viết để tránh các hành vi sẽ không được xác định trong một số triển khai, nhưng trong nhiều trường hợp khác, người ta chỉ cần đơn giản chỉ ra rằng mã không có khả năng được sử dụng cho các triển khai sẽ gây ra những điều quá khó chịu.

Lưu ý rằng việc sử dụng các loại như intshortcó thể loại bỏ một số cảnh báo và khắc phục một số vấn đề, nhưng có thể sẽ tạo ra các loại khác. Sự tương tác giữa các loại như uint16_tquy tắc quảng cáo số nguyên của C là tuyệt vời, nhưng những loại như vậy có lẽ vẫn tốt hơn bất kỳ thay thế nào.


6

1) Nếu bạn chỉ chuyển từ số nguyên không dấu sang số nguyên có cùng độ dài qua lại, không có bất kỳ thao tác nào ở giữa, bạn sẽ nhận được kết quả tương tự mọi lúc, vì vậy không có vấn đề gì ở đây. Nhưng các hoạt động logic và số học khác nhau đang hoạt động khác nhau trên các toán hạng đã ký và không dấu.
2) Lý do chính để sử dụng stdint.hcác loại là kích thước bit của các loại như vậy được xác định và bằng nhau trên tất cả các nền tảng, điều này không đúng int, longv.v., cũng như charkhông có dấu hiệu tiêu chuẩn, nó có thể được ký hoặc không dấu mặc định. Nó giúp dễ dàng hơn để thao tác dữ liệu biết kích thước chính xác mà không cần sử dụng kiểm tra và giả định thêm.


2
Các kích thước int32_tuint32_tbằng nhau trên tất cả các nền tảng mà chúng được xác định . Nếu bộ xử lý không có loại phần cứng phù hợp chính xác , các loại này không được xác định. Do đó, lợi thế của intvv và, có lẽ, int_least32_tv.v.
Pete Becker

1
@PeteBecker - Tuy nhiên, đó có thể là một lợi thế, bởi vì các lỗi biên dịch kết quả khiến bạn nhận ra ngay vấn đề. Tôi muốn thay vì các loại thay đổi kích thước của tôi trên tôi.
sapi

@sapi - trong nhiều tình huống kích thước cơ bản là không liên quan; Các lập trình viên C đã làm tốt với nhau mà không có kích thước cố định trong nhiều năm.
Pete Becker

6

Vì số 2 của Eugene có lẽ là điểm quan trọng nhất, tôi chỉ muốn nói thêm rằng đó là một lời khuyên trong

MISRA (directive 4.6): "typedefs that indicate size and signedness should be used in place of the basic types".

Ngoài ra, Jack Ganssle dường như là người ủng hộ quy tắc đó: http://www.ganssle.com/tem/tem265.html


2
Thật tệ khi không có bất kỳ loại nào để chỉ định "Số nguyên không dấu N-bit có thể được nhân một cách an toàn với bất kỳ số nguyên có cùng kích thước nào khác để mang lại kết quả có cùng kích thước". Quy tắc quảng bá số nguyên tương tác khủng khiếp với các loại hiện có như thế nào uint32_t.
supercat

3

Một cách dễ dàng để loại bỏ các cảnh báo là tránh sử dụng -Wconversion trong GCC. Tôi nghĩ rằng bạn phải kích hoạt tùy chọn này theo cách thủ công, nhưng nếu không, bạn có thể sử dụng -Wno-convert để tắt nó. Bạn có thể bật cảnh báo cho các chuyển đổi chính xác của dấu hiệu và FP thông qua các tùy chọn khác , nếu bạn vẫn muốn những chuyển đổi đó.

Các cảnh báo -Wconversion hầu như luôn luôn là dương tính giả, đó có thể là lý do tại sao thậm chí -Wextra không cho phép nó theo mặc định. Một câu hỏi Stack Overflow có rất nhiều gợi ý cho các bộ tùy chọn tốt. Dựa trên kinh nghiệm của riêng tôi, đây là một nơi tốt để bắt đầu:

-std = c99 -pedantic -Wall -Wextra -Wshadow

Thêm nhiều hơn nếu bạn cần chúng, nhưng tỷ lệ cược là bạn sẽ không.

Nếu bạn phải giữ -Wconversion, bạn có thể rút ngắn mã của mình một chút bằng cách chỉ đánh máy toán hạng số:

value16 <<= (uint16_t)8;
value8 += (uint8_t)2;

Điều đó không dễ đọc mà không cần tô sáng cú pháp.


2

trong bất kỳ dự án phần mềm nào, điều rất quan trọng là sử dụng các định nghĩa kiểu di động. (ngay cả phiên bản tiếp theo của cùng một trình biên dịch cũng cần sự xem xét này.) Một ví dụ hay, vài năm trước tôi đã làm một dự án trong đó trình biên dịch hiện tại định nghĩa 'int' là 8 bit. Phiên bản tiếp theo của trình biên dịch được định nghĩa 'int' là 16 bit. Vì chúng tôi đã không sử dụng bất kỳ định nghĩa di động nào cho 'int', ram (có hiệu quả) tăng gấp đôi kích thước và nhiều chuỗi mã phụ thuộc vào int 8 bit không thành công. Việc sử dụng định nghĩa kiểu di động sẽ tránh được vấn đề đó (hàng trăm giờ để khắc phục).


Không có mã hợp lý nên sử dụng intđể tham khảo loại 8 bit. Ngay cả khi một trình biên dịch không phải C như CCS làm như vậy, mã hợp lý vẫn nên sử dụng charhoặc loại được gõ cho 8 bit và loại được gõ (không phải "dài") cho 16 bit. Mặt khác, việc chuyển mã từ một cái gì đó như CCS sang một trình biên dịch thực sự có thể gây ra vấn đề ngay cả khi nó sử dụng các typedefs thích hợp, vì các trình biên dịch như vậy thường "không bình thường" theo những cách khác.
supercat

1
  1. Vâng. Một số nguyên có chữ ký n bit có thể đại diện cho khoảng một nửa số lượng số không âm dưới dạng số nguyên không dấu n bit và việc dựa vào các đặc tính tràn là hành vi không xác định để bất cứ điều gì cũng có thể xảy ra. Phần lớn các bộ xử lý hiện tại và quá khứ sử dụng bổ sung twos nên rất nhiều thao tác xảy ra tương tự đối với các loại tích phân đã ký và không dấu, nhưng ngay cả khi đó không phải tất cả các hoạt động sẽ tạo ra kết quả giống hệt nhau. Bạn thực sự yêu cầu thêm rắc rối sau này khi bạn không thể hiểu tại sao mã của bạn không hoạt động như dự định.

  2. Mặc dù intunsign có các kích thước được xác định khi triển khai, chúng thường được chọn "thông minh" bởi việc triển khai vì lý do kích thước hoặc tốc độ. Tôi thường bám vào những điều này trừ khi tôi có lý do chính đáng để làm khác. Tương tự như vậy, khi xem xét nên sử dụng int hay unsign, tôi thường thích int trừ khi tôi có lý do chính đáng để làm như vậy.

Trong trường hợp tôi thực sự cần kiểm soát tốt hơn kích thước hoặc ký hiệu của một loại, tôi thường sẽ thích sử dụng một typedef do hệ thống xác định (size_t, intmax_t, v.v.) hoặc tạo typedef của riêng tôi để chỉ ra chức năng của một loại đã cho loại (prng_int, adc_int, v.v.).


0

Thông thường mã được sử dụng trên ngón tay cái ARM và AVR (và x86, powerPC và các kiến ​​trúc khác), và 16 hoặc 32 bit có thể hiệu quả hơn (cả hai cách: flash và chu kỳ) trên STM32 ARM ngay cả đối với một biến phù hợp với 8 bit ( 8 bit là hiệu quả hơn trên AVR) . Tuy nhiên, nếu SRAM gần đầy, việc hoàn nguyên về 8 bit cho các vars toàn cầu có thể là hợp lý (nhưng không phải cho các vars cục bộ). Đối với tính di động và bảo trì (đặc biệt đối với các vars 8 bit), nó có các ưu điểm (không có bất kỳ nhược điểm nào) để chỉ định kích thước phù hợp MINIMUM , thay vì kích thước chính xác và typedef tại một vị trí .h (thường là dưới ifdef) để điều chỉnh (có thể là uint_fast8_t / uint_least8_t) trong thời gian chuyển / xây dựng, ví dụ:

// apparently uint16_t is just as efficient as 32 bit on STM32, but 8 bit is punished (with more flash and cycles)
typedef uint16_t uintG8_t; // 8bit if SRAM is scarce (use fol global vars that fit in 8 bit)
typedef uint16_t uintL8_t; // 8bit on AVR (local var, 16 or 32 bit is more efficient on STM + less flash)
// might better reserve 32 bits on some arch, STM32 seems efficient with 16 bits:
typedef uint16_t uintG16_t; // 16bit if SRAM is scarce (use fol global vars that fit in 16 bit)
typedef uint16_t uintL16_t; // 16bit on AVR (local var, 16 or 32 bit whichever is more efficient on other arch)

Thư viện GNU giúp một chút, nhưng thông thường các typedefs có ý nghĩa nào:

typedef uint_least8_t uintG8_t;
typedef uint_fast8_t uintL8_t;

// nhưng uint_fast8_t cho CẢ HAI khi SRAM không phải là vấn đề đáng lo ngại.

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.