Các thực hành tốt nhất liên quan đến ints không dấu là gì?


43

Tôi sử dụng số nguyên không dấu ở khắp mọi nơi và tôi không chắc mình có nên không. Điều này có thể từ các cột id khóa chính của cơ sở dữ liệu đến các bộ đếm, v.v ... Nếu một số không bao giờ âm, thì tôi sẽ luôn sử dụng một số nguyên không dấu.

Tuy nhiên tôi nhận thấy từ mã của người khác rằng dường như không ai khác làm điều này. Có điều gì quan trọng mà tôi đang xem không?

Chỉnh sửa: Vì câu hỏi này tôi cũng nhận thấy rằng trong C, việc trả về các giá trị âm cho lỗi là phổ biến thay vì đưa ra các ngoại lệ như trong C ++.


26
Chỉ cần coi chừng for(unsigned int n = 10; n >= 0; n --)(vòng lặp vô hạn)
Chris Burt-Brown

3
Trong C và C ++, ints không dấu có hành vi tràn được xác định chính xác (modulo 2 ^ n). Đã ký ints. Tối ưu hóa ngày càng khai thác rằng hành vi tràn không xác định, dẫn đến kết quả đáng ngạc nhiên trong một số trường hợp.
Steve314

2
Câu hỏi hay! Tôi cũng đã từng bị cám dỗ sử dụng phạm vi giới hạn của tint nhưng thấy rằng rủi ro / sự bất tiện đã vượt quá bất kỳ lợi ích / tiện lợi nào. Hầu hết các thư viện, như bạn đã nói, chấp nhận ints thường xuyên nơi một uint sẽ làm. Điều này làm cho nó khó khăn để làm việc với, nhưng cũng đặt ra câu hỏi: nó có đáng không? Trong thực tế (giả sử rằng bạn không đi về mọi thứ một cách ngớ ngẩn), bạn sẽ hiếm khi có giá trị -218 đến nơi mà một điều tích cực được mong đợi. Đó là -218 phải đến từ một nơi nào đó, phải không? và bạn có thể theo dõi nguồn gốc của nó. Xảy ra hiếm khi. Sử dụng các xác nhận, ngoại lệ, hợp đồng mã để hỗ trợ bạn.
Công việc

@William Ting: Nếu đây chỉ là về C / C ++, bạn nên thêm các thẻ thích hợp vào câu hỏi của mình.
CesarGon

2
@Chris: Làm thế nào quan trọng là vấn đề vòng lặp vô hạn trong thực tế? Ý tôi là, nếu nó được phát hành, thì rõ ràng mã không được kiểm tra. Ngay cả khi bạn cần một vài giờ để gỡ lỗi trong lần đầu tiên bạn mắc lỗi này, lần thứ hai bạn nên biết những gì cần tìm trước tiên khi mã của bạn không ngừng lặp.
Bảo mật

Câu trả lời:


28

Có điều gì quan trọng mà tôi đang xem không?

Khi các tính toán liên quan đến cả loại đã ký và loại không dấu cũng như các kích thước khác nhau, các quy tắc cho quảng cáo loại có thể phức tạp và dẫn đến hành vi không mong muốn .

Tôi tin rằng đây là lý do chính tại sao Java bỏ qua các kiểu int không dấu.


3
Một giải pháp khác là yêu cầu bạn tự đúc số của mình cho phù hợp. Đây là những gì Go dường như làm (tôi chỉ chơi xung quanh với nó một chút thôi) và tôi thích nó hơn là cách tiếp cận của Java.
Tikhon Jelvis

2
Đó là một lý do chính đáng để Java không bao gồm loại không dấu 64 bit và có thể là một lý do hợp lý để không bao gồm loại không dấu 32 bit [mặc dù ngữ nghĩa của việc thêm các giá trị 32 bit đã ký và không dấu sẽ khó khăn-- một hoạt động như vậy chỉ đơn giản sẽ mang lại kết quả được ký 64 bit]. intTuy nhiên, các loại không được gán nhỏ hơn sẽ không gây khó khăn như vậy (vì bất kỳ tính toán nào sẽ được quảng bá int); Tôi không có gì tốt để nói về việc thiếu một loại byte không dấu.
supercat

17

Tôi nghĩ rằng Michael có một điểm hợp lệ, nhưng IMO lý do tại sao mọi người sử dụng int mọi lúc (đặc biệt là for (int i = 0; i < max, i++) là chúng tôi đã học được theo cách đó. Khi mỗi ví dụ đơn lẻ trong một cuốn sách ' làm thế nào để học lập trình ' sử dụng inttrong một forvòng lặp, rất ít người sẽ đặt câu hỏi cho việc thực hành đó.

Lý do khác là nó intngắn hơn 25% uintvà tất cả chúng ta đều lười biếng ... ;-)


2
Tôi đồng ý với vấn đề giáo dục. Hầu hết mọi người dường như không bao giờ đặt câu hỏi về những gì họ đọc: Nếu nó trong một cuốn sách, nó không thể sai, phải không?
Matthieu M.

1
Đó cũng có thể là lý do tại sao mọi người sử dụng postfix ++khi tăng, mặc dù thực tế là hành vi cụ thể của nó hiếm khi cần thiết và thậm chí có thể dẫn đến việc vô hiệu hóa các bản sao nếu chỉ số vòng lặp là một trình lặp hoặc loại không cơ bản khác (hoặc trình biên dịch thực sự dày đặc) .
gạch dưới

Đừng làm điều gì đó như "for (uint i = 10; i> = 0; --i)". Chỉ sử dụng ints cho các biến vòng lặp sẽ tránh khả năng này.
David Thornley


8

Trộn các loại có chữ ký và không dấu có thể đưa bạn vào một thế giới đau khổ. Và bạn không thể sử dụng tất cả các loại không dấu vì bạn sẽ gặp những thứ có phạm vi hợp lệ bao gồm số âm hoặc cần một giá trị để chỉ ra lỗi và -1 là tự nhiên nhất. Vì vậy, kết quả cuối cùng là nhiều lập trình viên sử dụng tất cả các loại số nguyên đã ký.


1
Có lẽ đó là một cách thực hành tốt hơn để không trộn lẫn các giá trị hợp lệ với dấu hiệu lỗi trong cùng một biến và sử dụng các biến riêng biệt cho điều này. Cấp, thư viện tiêu chuẩn C không làm gương tốt ở đây.
Bảo mật

7

Đối với tôi các loại là nhiều về giao tiếp. Bằng cách sử dụng một cách rõ ràng một int unsign bạn nói với tôi rằng các giá trị đã ký không phải là giá trị hợp lệ. Điều này cho phép tôi thêm một số thông tin khi đọc mã của bạn ngoài tên biến. Lý tưởng nhất là một loại không ẩn danh sẽ cho tôi biết nhiều hơn, nhưng nó cung cấp cho tôi nhiều thông tin hơn nếu bạn đã sử dụng ints ở mọi nơi.

Thật không may, không phải ai cũng rất ý thức về những gì mã của họ truyền đạt, và đó có lẽ là lý do bạn thấy ints ở khắp mọi nơi mặc dù các giá trị ít nhất là không dấu.


4
Nhưng tôi có thể muốn giới hạn giá trị của mình trong một tháng chỉ từ 1 đến 12. Tôi có sử dụng loại khác cho nó không? Một tháng thì sao? Một số ngôn ngữ thực sự cho phép hạn chế các giá trị như thế. Những người khác, chẳng hạn như .Net / C # cung cấp Hợp đồng mã. Chắc chắn, số nguyên không âm xảy ra khá thường xuyên, nhưng hầu hết các ngôn ngữ hỗ trợ loại này không hỗ trợ các hạn chế hơn nữa. Vì vậy, người ta nên sử dụng kết hợp các gợi ý và kiểm tra lỗi, hay chỉ cần làm mọi thứ thông qua kiểm tra lỗi? Hầu hết các thư viện không yêu cầu uint sử dụng cái nào hợp lý, do đó sử dụng một và việc truyền có thể gây bất tiện.
Công việc

@Job Tôi sẽ nói bạn nên sử dụng một số loại hạn chế trình biên dịch / trình thông dịch bắt buộc trong các tháng của bạn. Nó có thể cung cấp cho bạn một số mẫu soạn sẵn để thiết lập, nhưng trong tương lai bạn có một hạn chế được thi hành nhằm ngăn ngừa lỗi và truyền đạt rõ ràng hơn nhiều những gì bạn đang mong đợi. Ngăn chặn lỗi và giảm bớt giao tiếp là quan trọng hơn nhiều so với sự bất tiện trong khi thực hiện.
daramarak

1
"Tôi có thể muốn giới hạn các giá trị của mình trong một tháng chỉ từ 1 đến 12" Nếu bạn có một bộ giá trị hữu hạn như tháng, bạn nên sử dụng loại liệt kê, không phải số nguyên.
Josh Caswell

6

Tôi sử dụng unsigned inttrong C ++ cho các chỉ số mảng, chủ yếu và cho bất kỳ bộ đếm nào bắt đầu từ 0. Tôi nghĩ rằng thật tốt khi nói rõ ràng "biến này không thể âm".


14
Có lẽ bạn nên sử dụng size_t cho điều này trong c ++
JohnB

2
Tôi biết, tôi không thể bị làm phiền.
quant_dev

3

Bạn nên quan tâm đến điều này khi bạn đang xử lý một số nguyên thực sự có thể tiếp cận hoặc vượt quá giới hạn của một số nguyên đã ký. Vì mức tối đa dương của số nguyên 32 bit là 2.147.483.647, nên bạn nên sử dụng số nguyên không dấu nếu bạn biết nó sẽ không bao giờ âm và b) có thể đạt 2.147.483.648. Trong hầu hết các trường hợp, bao gồm các khóa và bộ đếm cơ sở dữ liệu, tôi thậm chí sẽ không bao giờ tiếp cận các loại số này vì vậy tôi không bận tâm đến việc lo lắng liệu bit dấu được sử dụng cho một giá trị số hay để chỉ ra dấu hiệu.

Tôi sẽ nói: sử dụng int trừ khi bạn biết bạn cần một số nguyên không dấu.


2
Khi làm việc với các giá trị có thể đạt đến các giá trị tối đa, bạn nên bắt đầu kiểm tra các hoạt động xem có tràn số nguyên hay không, bất kể dấu hiệu nào. Các kiểm tra này thường dễ dàng hơn đối với các loại không dấu, bởi vì hầu hết các hoạt động có kết quả được xác định rõ mà không xác định và thực hiện hành vi được xác định.
Bảo mật

3

Đó là sự đánh đổi giữa sự đơn giản và độ tin cậy. Càng nhiều lỗi có thể bị bắt tại thời điểm biên dịch, phần mềm càng đáng tin cậy. Những người và tổ chức khác nhau ở những điểm khác nhau dọc theo quang phổ đó.

Nếu bạn từng thực hiện bất kỳ chương trình có độ tin cậy cao nào trong Ada, bạn thậm chí sử dụng các loại khác nhau cho các biến như khoảng cách tính bằng feet so với khoảng cách tính bằng mét và trình biên dịch gắn cờ nó nếu bạn vô tình gán cái này cho cái kia. Điều đó hoàn hảo để lập trình một tên lửa dẫn đường, nhưng quá mức cần thiết (ý định chơi chữ) nếu bạn xác nhận một biểu mẫu web. Không nhất thiết có bất cứ điều gì sai với bất kỳ cách nào miễn là phù hợp với yêu cầu.


2

Tôi có khuynh hướng đồng ý với lý luận của Joel Etherton, nhưng đi đến kết luận ngược lại. Theo cách tôi nhìn thấy, ngay cả khi bạn biết rằng các con số không bao giờ đạt đến giới hạn của loại đã ký, nếu bạn biết rằng số âm sẽ không xảy ra, thì có rất ít lý do để sử dụng biến thể đã ký của loại.

Vì lý do tương tự tại sao tôi có, trong một vài trường hợp chọn, được sử dụng BIGINT(số nguyên 64 bit) thay vì INTEGER(số nguyên 32 bit) trong các bảng SQL Server. Xác suất dữ liệu sẽ đạt đến giới hạn 32 bit trong bất kỳ khoảng thời gian hợp lý nào là rất nhỏ, nhưng nếu nó xảy ra, hậu quả trong một số tình huống có thể rất nghiêm trọng. Chỉ cần chắc chắn ánh xạ các loại giữa các ngôn ngữ một cách chính xác, hoặc bạn sẽ kết thúc với sự kỳ lạ thú vị thực sự ở phía xa ...

Điều đó nói rằng, đối với một số thứ, chẳng hạn như các giá trị khóa chính của cơ sở dữ liệu, đã ký hoặc không dấu thực sự không thành vấn đề, bởi vì trừ khi bạn đang tự sửa chữa dữ liệu bị hỏng hoặc một cái gì đó dọc theo các dòng đó, bạn sẽ không bao giờ xử lý trực tiếp giá trị; nó là một định danh, không có gì hơn. Trong những trường hợp đó, tính nhất quán có lẽ quan trọng hơn sự lựa chọn chính xác của chữ ký. Mặt khác, bạn kết thúc với một số cột khóa ngoại được ký và các cột khác không dấu, không có mẫu rõ ràng cho nó - hoặc sự kỳ lạ thú vị đó một lần nữa.


Nếu bạn đang làm việc với dữ liệu được trích xuất từ ​​hệ thống SAP, tôi thực sự khuyên bạn nên BIGINT cho các trường ID (như CustomerNumber, ArticleNumber, v.v.). Miễn là không ai sử dụng chuỗi ký tự chữ và số làm ID, đó là ... thở dài
Treb

1

Tôi muốn khuyến nghị rằng các bối cảnh lưu trữ dữ liệu và trao đổi dữ liệu bị hạn chế ngoài không gian, người ta thường nên sử dụng các loại đã ký. Trong hầu hết các trường hợp số nguyên có chữ ký 32 bit quá nhỏ nhưng giá trị không dấu 32 bit sẽ đủ cho ngày hôm nay, sẽ không lâu nữa trước khi giá trị không dấu 32 bit không đủ lớn.

Lần đầu tiên người ta nên sử dụng các loại không dấu là khi một trong hai tập hợp nhiều giá trị thành một giá trị lớn hơn (ví dụ: chuyển đổi bốn byte thành số 32 bit) hoặc phân tách các giá trị lớn hơn thành các giá trị nhỏ hơn (ví dụ: lưu trữ một số 32 bit thành bốn byte ) hoặc khi một người có số lượng dự kiến ​​sẽ "lăn" theo định kỳ và người ta cần phải đối phó với nó (nghĩ về đồng hồ tiện ích dân cư; hầu hết trong số họ có đủ chữ số để đảm bảo rằng họ sẽ không thể cuộn giữa các lần đọc nếu họ đọc ba lần một năm, nhưng không đủ để đảm bảo họ sẽ không lăn lộn trong vòng đời hữu ích của máy đo). Các loại không được ký thường có đủ 'sự kỳ lạ' mà chúng chỉ nên được sử dụng trong trường hợp ngữ nghĩa của chúng là cần thiết.


1
"Tôi muốn giới thiệu [...] thường sử dụng các loại đã ký." Hừm, bạn đã quên đề cập đến những lợi thế của các loại đã ký và chỉ đưa ra một danh sách khi nào nên sử dụng các loại không dấu. "Lạ" ? Mặc dù hầu hết các hoạt động chưa được ký có hành vi và kết quả được xác định rõ, bạn nhập hành vi không xác định và thực hiện được xác định khi sử dụng các loại đã ký (tràn, dịch chuyển bit, ...). Bạn có một định nghĩa kỳ lạ về "sự kỳ lạ" ở đây.
Bảo mật

1
@Secure: "Sự kỳ lạ" mà tôi đề cập có liên quan đến ngữ nghĩa của các toán tử so sánh, đặc biệt là trong các hoạt động liên quan đến các loại ký và không dấu hỗn hợp. Bạn đúng rằng hành vi của các loại đã ký không được xác định khi sử dụng các giá trị đủ lớn để tràn, nhưng hành vi của các loại không dấu có thể gây ngạc nhiên ngay cả khi xử lý các số tương đối nhỏ. Ví dụ: (-3) + (1u) lớn hơn -1. Ngoài ra, một số quan hệ liên kết toán học thông thường sẽ áp dụng cho các số không áp dụng cho không dấu. Ví dụ: (ab)> c không ngụ ý (ac)> b.
supercat

1
@Secure: Mặc dù đúng là người ta không thể luôn dựa vào hành vi liên kết như vậy với số đã ký "lớn", nhưng các hành vi hoạt động như mong đợi khi xử lý các số "nhỏ" so với miền của số nguyên đã ký. Ngược lại, sự không liên kết được đề cập ở trên có vấn đề với các giá trị không dấu "2 3 1". Ngẫu nhiên, thực tế là các hành vi đã ký có hành vi không xác định khi được sử dụng ngoài giới hạn có thể cho phép tạo mã được cải thiện trên một số nền tảng khi sử dụng các giá trị nhỏ hơn kích thước từ gốc.
supercat

1
Nếu những bình luận này có trong câu trả lời của bạn ngay từ đầu, thay vì một lời đề nghị và "gọi tên" mà không đưa ra bất kỳ lý do nào, tôi sẽ không bình luận nó. ;) Mặc dù tôi vẫn không đồng ý với "sự kỳ lạ" ở đây, nó chỉ đơn giản là định nghĩa của loại. Sử dụng công cụ phù hợp cho công việc nhất định, và biết công cụ, tất nhiên. Các loại không được ký là công cụ sai khi bạn cần quan hệ +/-. Có một lý do tại sao size_tkhông dấu và ptrdiff_tđược ký.
Bảo mật

1
@Secure: Nếu những gì người ta muốn là đại diện cho một chuỗi các bit, các loại không dấu là tuyệt vời; Tôi nghĩ rằng chúng tôi đồng ý ở đó. Và trên một số kính hiển vi nhỏ, các loại không dấu có thể hiệu quả hơn đối với số lượng. Chúng cũng hữu ích trong trường hợp deltas đại diện cho số lượng nhưng giá trị thực tế không (ví dụ: số thứ tự TCP). Mặt khác, bất cứ khi nào trừ đi các giá trị không dấu, người ta phải lo lắng về các trường hợp góc ngay cả khi các số nhỏ; toán học như vậy với các giá trị đã ký chỉ trình bày các trường hợp góc khi số lớn.
supercat

1

Tôi sử dụng ints không dấu để làm cho mã của tôi và ý định của nó rõ ràng hơn. Một điều tôi làm để bảo vệ chống lại các chuyển đổi ngầm bất ngờ khi thực hiện số học với cả hai loại đã ký và không dấu là sử dụng một ký hiệu ngắn không dấu (thường là 2 byte) cho các biến không dấu của tôi. Điều này có hiệu quả vì một vài lý do:

  • Khi bạn thực hiện số học với các biến và ký tự ngắn không dấu (thuộc kiểu int) hoặc biến kiểu int, điều này đảm bảo biến không dấu sẽ luôn được thăng cấp thành int trước khi đánh giá biểu thức, vì int luôn có thứ hạng cao hơn ngắn . Điều này tránh mọi hành vi không mong muốn thực hiện số học với các loại đã ký và không dấu, giả sử kết quả của biểu thức phù hợp với một int đã ký tất nhiên.
  • Hầu hết thời gian, các biến không dấu mà bạn đang sử dụng sẽ không vượt quá giá trị tối đa của một byte ngắn 2 byte không dấu (65,535)

Nguyên tắc chung là loại biến không dấu của bạn phải có thứ hạng thấp hơn loại biến được ký để đảm bảo thăng hạng cho loại đã ký. Sau đó, bạn sẽ không có bất kỳ hành vi tràn bất ngờ. Rõ ràng là bạn không thể đảm bảo điều này mọi lúc, nhưng (hầu hết) thường là khả thi để đảm bảo điều này.

Ví dụ, gần đây tôi có một số vòng lặp như thế này:

const unsigned short cuint = 5;
for(unsigned short i=0; i<10; ++i)
{
    if((i-2)%cuint == 0)
    {
       //Do something
    }
}

Chữ "2" theo kiểu int. Nếu tôi là một số nguyên không dấu thay vì một ký hiệu không dấu, thì trong biểu thức con (i-2), 2 sẽ được thăng cấp thành một số không dấu (vì số nguyên không dấu có mức độ ưu tiên cao hơn số nguyên được ký). Nếu i = 0, thì biểu thức con bằng (0u-2u) = một số giá trị lớn do tràn. Cùng một ý tưởng với i = 1. Tuy nhiên, vì tôi là một dấu ngắn không dấu, nên nó được quảng cáo cùng loại với nghĩa đen là '2', được ký tên int và mọi thứ đều hoạt động tốt.

Để an toàn hơn: trong trường hợp hiếm khi kiến ​​trúc bạn triển khai với nguyên nhân int là 2 byte, điều này có thể khiến cả hai toán hạng trong biểu thức số học được thăng cấp thành int unsign trong trường hợp biến ngắn không dấu không khớp vào int 2 byte đã ký, cái sau có giá trị tối đa là 32.767 <65.535. (Xem https://stackoverflow.com/questions/17832815/c-implicit-conversion-sign-unign để biết thêm chi tiết). Để chống lại điều này, bạn chỉ cần thêm static_assert vào chương trình của mình như sau:

static_assert(sizeof(int) == 4, "int must be 4 bytes");

và nó sẽ không biên dịch trên các kiến ​​trúc trong đó int là 2 byte.

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.