uint8_t vs char không dấu


231

Lợi thế của việc sử dụng là gì uint8_tso với unsigned chartrong C?

Tôi biết rằng trên hầu hết mọi hệ thống uint8_tchỉ là một typedef unsigned char, vậy tại sao lại sử dụng nó?

Câu trả lời:


225

Nó ghi lại ý định của bạn - bạn sẽ lưu trữ những con số nhỏ, thay vì một ký tự.

Ngoài ra, nó trông đẹp hơn nếu bạn đang sử dụng các typedefs khác như uint16_thoặc int32_t.


1
Không rõ ràng trong câu hỏi ban đầu nếu chúng ta đang nói về một loại tiêu chuẩn hay không. Tôi chắc chắn đã có nhiều biến thể của quy ước đặt tên này trong những năm qua.
Đánh dấu tiền chuộc

8
Hoàn toàn sử dụng unsigned charhoặc signed charghi lại ý định, vì không charđược cung cấp là những gì cho thấy bạn đang làm việc với các nhân vật.
phê

9
Tôi nghĩ rằng một định nghĩa không unsignedđược trang bị là unsigned inttheo định nghĩa?
Đánh dấu tiền chuộc

5
@endolith, sử dụng uint8_t cho một chuỗi không nhất thiết là sai, nhưng nó thực sự kỳ lạ.
Đánh dấu tiền chuộc

5
@endolith, tôi nghĩ rằng tôi có thể tạo ra một trường hợp cho uint8_t với văn bản UTF8. Thật vậy, chardường như ngụ ý một ký tự, trong khi trong ngữ cảnh của chuỗi UTF8, nó có thể chỉ là một byte của một ký tự đa dòng. Sử dụng uint8_t có thể làm rõ rằng người ta không nên mong đợi một ký tự ở mọi vị trí - nói cách khác, mỗi phần tử của chuỗi / mảng là một số nguyên tùy ý mà người ta không nên đưa ra bất kỳ giả định ngữ nghĩa nào. Tất nhiên tất cả các lập trình viên C đều biết điều này, nhưng nó có thể thúc đẩy những người mới bắt đầu hỏi đúng câu hỏi.
tne

69

Chỉ là phạm vi, một số hệ thống có thể không có loại 8 bit. Theo Wikipedia :

Việc triển khai là bắt buộc để xác định các loại số nguyên có chiều rộng chính xác cho N = 8, 16, 32 hoặc 64 khi và chỉ khi nó có bất kỳ loại nào đáp ứng yêu cầu. Không bắt buộc phải định nghĩa chúng cho bất kỳ N nào khác, ngay cả khi nó hỗ trợ các loại thích hợp.

Vì vậy, uint8_tkhông được đảm bảo tồn tại, mặc dù nó sẽ dành cho tất cả các nền tảng có 8 bit = 1 byte. Một số nền tảng nhúng có thể khác nhau, nhưng điều đó rất hiếm. Một số hệ thống có thể định nghĩa charcác loại là 16 bit, trong trường hợp đó có thể sẽ không có loại 8 bit nào.

Ngoài vấn đề (nhỏ) đó, câu trả lời của @Mark Ransom là tốt nhất theo quan điểm của tôi. Sử dụng dữ liệu thể hiện rõ nhất những gì bạn đang sử dụng dữ liệu.

Ngoài ra, tôi giả sử bạn có nghĩa là uint8_t(typedef tiêu chuẩn từ C99 được cung cấp trong stdint.htiêu đề) chứ không phải uint_8(không phải là một phần của bất kỳ tiêu chuẩn nào).


3
@caf, vì tò mò tuyệt đối - bạn có thể liên kết đến mô tả của một số? Tôi biết chúng tồn tại bởi vì ai đó đã đề cập đến một (và được liên kết với tài liệu dành cho nhà phát triển cho nó) trong một comp.lang.c ++. Cuộc thảo luận được kiểm duyệt về việc đảm bảo loại C / C ++ có quá yếu hay không, nhưng tôi không thể tìm thấy chủ đề đó nữa và nó luôn tiện dụng để tham khảo rằng trong bất kỳ cuộc thảo luận tương tự nào :)
Pavel Minaev

3
"Một số hệ thống có thể định nghĩa các loại char là 16 bit, trong trường hợp đó có thể sẽ không có loại 8 bit nào." - và mặc dù có một số phản đối không chính xác từ tôi, Pavel đã chứng minh trong câu trả lời của mình rằng nếu char là 16 bit, thì ngay cả khi trình biên dịch cung cấp loại 8 bit, thì nó cũng không được gọi nó uint8_t(hoặc gõ nó theo cách đó). Điều này là do loại 8 bit sẽ có các bit không được sử dụng trong biểu diễn lưu trữ, mà uint8_tkhông phải có.
Steve Jessop

3
Kiến trúc SHARC có các từ 32 bit. Xem en.wikipedia.org/wiki/ Nhật để biết chi tiết.
BCran

2
Và các C5000 DSP của TI (có trong OMAP1 và OMAP2) là 16 bit. Tôi nghĩ với OMAP3, họ đã đi đến C6000-series, với một char 8 bit.
Steve Jessop

4
Đi sâu vào N3242 - "Bản nháp làm việc, Tiêu chuẩn cho ngôn ngữ lập trình C ++", phần 18.4.1 <cstdint> tóm tắt nói - typedef unsigned integer type uint8_t; // optional Vì vậy, về bản chất, không cần một thư viện tuân thủ tiêu chuẩn C ++ để xác định uint8_t (xem bình luận // tùy chọn // )
nightlytrails 23/213

43

Toàn bộ vấn đề là viết mã độc lập thực hiện. unsigned charkhông được đảm bảo là loại 8 bit. uint8_tlà (nếu có).


4
... Nếu nó tồn tại trên một hệ thống, nhưng điều đó sẽ rất hiếm. +1
Chris Lutz

2
tốt nếu bạn thực sự gặp rắc rối với mã của mình không biên dịch trên hệ thống vì uint8_t không tồn tại, bạn có thể sử dụng find và sed để tự động thay đổi tất cả các lần xuất hiện của uint8_t thành char không dấu hoặc một cái gì đó hữu ích hơn cho bạn.
bazz

2
@bazz - không phải nếu bạn cho rằng đó là loại 8 bit mà bạn không thể - ví dụ: để giải nén dữ liệu được đóng gói theo kiểu tạm thời bằng một hệ thống từ xa. Giả định ngầm định là lý do uint8_t không tồn tại là trên một bộ xử lý có char hơn 8 bit.
Chris Stratton

đưa vào khẳng định khẳng định (sizeof (uns uns char) == 8);
bazz

3
@bazz khẳng định không chính xác tôi sợ. sizeof(unsigned char)sẽ trả về 11 byte. nhưng nếu một char hệ thống và int có cùng kích thước, ví dụ 16 bit thì sizeof(int)cũng sẽ quay trở lại1
Toby

7

Như bạn đã nói, " hầu hết mọi hệ thống".

charcó lẽ là một trong những ít có khả năng thay đổi, nhưng một khi bạn bắt đầu sử dụng uint16_tvà bạn bè, sử dụng uint8_thỗn hợp tốt hơn, và thậm chí có thể là một phần của tiêu chuẩn mã hóa.


7

Theo kinh nghiệm của tôi, có hai nơi chúng tôi muốn sử dụng uint8_t có nghĩa là 8 bit (và uint16_t, v.v.) và nơi chúng tôi có thể có các trường nhỏ hơn 8 bit. Cả hai vị trí là nơi không gian quan trọng và chúng ta thường cần xem xét một bãi dữ liệu thô khi gỡ lỗi và cần có thể nhanh chóng xác định những gì nó đại diện.

Đầu tiên là trong các giao thức RF, đặc biệt là trong các hệ thống băng tần hẹp. Trong môi trường này, chúng ta có thể cần phải đóng gói càng nhiều thông tin càng tốt vào một tin nhắn. Thứ hai là trong bộ lưu trữ flash, nơi chúng ta có thể có không gian rất hạn chế (chẳng hạn như trong các hệ thống nhúng). Trong cả hai trường hợp, chúng tôi có thể sử dụng cấu trúc dữ liệu được đóng gói trong đó trình biên dịch sẽ đảm nhiệm việc đóng gói và giải nén cho chúng tôi:

#pragma pack(1)
typedef struct {
  uint8_t    flag1:1;
  uint8_t    flag2:1;
  padding1   reserved:6;  /* not necessary but makes this struct more readable */
  uint32_t   sequence_no;
  uint8_t    data[8];
  uint32_t   crc32;
} s_mypacket __attribute__((packed));
#pragma pack()

Phương pháp bạn sử dụng phụ thuộc vào trình biên dịch của bạn. Bạn cũng có thể cần hỗ trợ một số trình biên dịch khác nhau với cùng một tệp tiêu đề. Điều này xảy ra trong các hệ thống nhúng, nơi các thiết bị và máy chủ có thể hoàn toàn khác nhau - ví dụ: bạn có thể có một thiết bị ARM giao tiếp với máy chủ Linux x86.

Có một vài cảnh báo với việc sử dụng các cấu trúc đóng gói. Gotcha lớn nhất là bạn phải tránh hội thảo địa chỉ của một thành viên. Trên các hệ thống có các từ được sắp xếp bằng mutibyte, điều này có thể dẫn đến một ngoại lệ bị sai lệch - và bị loại bỏ.

Một số người cũng sẽ lo lắng về hiệu suất và lập luận rằng việc sử dụng các cấu trúc đóng gói này sẽ làm chậm hệ thống của bạn. Đúng là, đằng sau hậu trường, trình biên dịch thêm mã để truy cập các thành viên dữ liệu chưa được phân bổ. Bạn có thể thấy điều đó bằng cách nhìn vào mã lắp ráp trong IDE của bạn.

Nhưng vì các cấu trúc đóng gói là hữu ích nhất cho việc lưu trữ và lưu trữ dữ liệu, sau đó dữ liệu có thể được trích xuất thành một biểu diễn không được đóng gói khi làm việc với nó trong bộ nhớ. Thông thường chúng ta không cần phải làm việc với toàn bộ gói dữ liệu trong bộ nhớ.

Đây là một số thảo luận có liên quan:

gói pragma (1) cũng không __attribution__ ((căn chỉnh (1))) hoạt động

Là __attribution __ ((đóng gói)) / #pragma của gcc có an toàn không?

http://solidsmoke.blogspot.ca/2010/07/woes-of-structure-packing-pragma-pack.html


6

Có rất ít. Từ quan điểm tính di động, charkhông thể nhỏ hơn 8 bit và không có gì có thể nhỏ hơn char, vì vậy nếu một triển khai C đã cho có loại số nguyên 8 bit không dấu, thì nó sẽ như vậy char. Ngoài ra, nó có thể không có một chút nào, tại thời điểm đó, bất kỳ typedefthủ thuật nào cũng được đưa ra.

Nó có thể được sử dụng để ghi lại mã của bạn tốt hơn theo nghĩa rõ ràng rằng bạn yêu cầu byte 8 bit ở đó và không có gì khác. Nhưng trong thực tế, đó là một kỳ vọng hợp lý hầu như ở bất cứ đâu đã có (có các nền tảng DSP không đúng sự thật, nhưng khả năng mã của bạn chạy ở đó rất mong manh và bạn cũng có thể gặp lỗi khi sử dụng một xác nhận tĩnh ở đầu chương trình của bạn trên một nền tảng như vậy).


7
@Skizz - Không, tiêu chuẩn yêu cầu unsigned charcó thể giữ các giá trị trong khoảng từ 0 đến 255. Nếu bạn có thể làm điều đó trong 4 bit, mũ của tôi sẽ tắt cho bạn.
Chris Lutz

1
"nó sẽ cồng kềnh hơn một chút" - cồng kềnh theo nghĩa là bạn phải đi bộ (bơi, bắt máy bay, v.v.) suốt quãng đường tới nơi người viết trình biên dịch, tát chúng sau gáy và làm cho chúng thêm uint8_tvào để thực hiện. Tôi tự hỏi, các trình biên dịch cho DSP có ký tự 16 bit thường thực hiện uint8_thay không?
Steve Jessop

6
Nhân tiện, trên một ý nghĩ thứ hai, có lẽ đó là cách đơn giản nhất để nói "Tôi thực sự cần 8 bit" - #include <stdint.h>và sử dụng uint8_t. Nếu nền tảng có nó, nó sẽ cung cấp cho bạn. Nếu nền tảng không có nó, chương trình của bạn sẽ không biên dịch và lý do sẽ rõ ràng và đơn giản.
Pavel Minaev

2
Vẫn không có điếu xì gà, xin lỗi: "Đối với các loại số nguyên không dấu khác với char không dấu, các bit của biểu diễn đối tượng sẽ được chia thành hai nhóm: bit giá trị và bit đệm ... Nếu có bit giá trị N, mỗi bit sẽ đại diện cho một loại khác nhau sức mạnh của 2 từ 1 đến 2 ^ (N-1), do đó các đối tượng thuộc loại đó sẽ có khả năng biểu diễn các giá trị từ 0 đến 2 ^ (N-1) bằng cách sử dụng biểu diễn nhị phân thuần túy ... Tên typedef intN_t chỉ định a loại số nguyên đã ký với chiều rộng N, không có bit đệm và biểu diễn phần bù hai. "
Pavel Minaev

1
Nếu bạn chỉ cần modulo số học, bitfield không dấu sẽ hoạt động tốt (nếu bất tiện). Đó là khi bạn cần, giả sử, một mảng các octet không có phần đệm, đó là khi bạn là SOL. Đạo đức của câu chuyện không phải là mã hóa cho DSP, và tuân theo các kiến ​​trúc char 8 bit đúng đắn, trung thực với Chúa :)
Pavel Minaev

4

Điều đó thực sự quan trọng, ví dụ khi bạn đang viết một bộ phân tích mạng. tiêu đề gói được xác định bởi đặc tả giao thức, không phải bằng cách trình biên dịch C của một nền tảng cụ thể hoạt động.


Quay lại khi tôi hỏi điều này, tôi đã xác định một giao thức đơn giản cho giao tiếp qua nối tiếp.
Lyndon White

2

Trên hầu hết mọi hệ thống tôi đã gặp uint8_t == char không dấu, nhưng điều này không được đảm bảo bởi tiêu chuẩn C. Nếu bạn đang cố gắng viết mã di động và vấn đề chính xác là kích thước bộ nhớ, hãy sử dụng uint8_t. Nếu không sử dụng char không dấu.


3
uint8_t luôn khớp với phạm vi và kích thước unsigned charvà phần đệm (không có) khi unsigned char là 8 bit. Khi unsigned charkhông phải là 8 bit, uint8_tkhông tồn tại.
chux - Phục hồi Monica

@chux, Bạn có tham khảo địa điểm chính xác trong tiêu chuẩn mà nó nói không? Nếu unsigned charlà 8 bit, được uint8_tđảm bảo là một số typedefchứ không phải là typedefmột kiểu số nguyên không dấu mở rộng ?
hsivonen

@hsivonen "vị trí chính xác trong tiêu chuẩn nơi nó nói điều đó?" -> Không - chưa tìm đến 7.20.1.1. Nó dễ dàng được suy luận là unsigned char/signed char/charloại nhỏ nhất - không nhỏ hơn 8 bit. unsigned charkhông có đệm. Để uint8_tđược, nó phải là 8 bit, không có phần đệm, tồn tại do một kiểu số nguyên được cung cấp: phù hợp với các yêu cầu tối thiểu của unsigned char. Đối với "... đảm bảo là một typedef ..." có vẻ như là một câu hỏi hay để đăng.
chux - Phục hồi lạ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.