2009-06-12 40 views
20

Tôi đã đọc Tiêu chuẩn C vào ngày khác, và nhận thấy rằng không giống như tràn số nguyên đã ký (không xác định), tràn số nguyên không dấu được xác định rõ. Tôi đã nhìn thấy nó được sử dụng trong rất nhiều mã cho tối đa, vv nhưng cho voodoos về tràn, điều này được coi là thực hành lập trình tốt? Liệu nó có an toàn không? Tôi biết rằng rất nhiều ngôn ngữ hiện đại như Python không hỗ trợ nó - thay vào đó chúng tiếp tục mở rộng kích thước của các số lớn.Có sử dụng thực thi không đúng số nguyên không?

Trả lời

20

Luồng số nguyên chưa được ký (trong hình dạng quấn quanh) thường được tận dụng trong các hàm băm, và đã kể từ dấu chấm năm.

+1

năm chấm: Đó có phải là năm 1970 không? –

+0

Khi nguồn gốc của máy tính là trong mật mã, tôi nghi ngờ nó là đôi khi trong những năm 1940. –

+0

Mặc dù tôi không thực sự nhận được lý do tại sao nhấn mạnh vào * ký * số nguyên tràn. Sử dụng một số nguyên không dấu chỉ là tốt và có thể được làm việc xung quanh để phát hiện tràn. –

3

Một cách mà tôi có thể nghĩ về tràn số nguyên không dấu gây ra một vấn đề là khi trừ đi một giá trị nhỏ chưa ký, dẫn đến việc nó được gói vào một giá trị dương lớn.

lời khuyên thiết thực cho tràn số nguyên:
http://www.gnu.org/software/hello/manual/autoconf/Integer-Overflow-Basics.html#Integer-Overflow-Basics

+1

Tôi đang hỏi về tràn chưa được ký – user122147

+0

Điều này chưa được ký. Sử dụng số học chưa ký, trừ 200 từ 100. Bạn sẽ nhận được một giá trị lớn (mức độ lớn phụ thuộc vào phạm vi giá trị). Unithigned số học trong C được định nghĩa là mô-đun, hoặc bạn có thể nói quấn xung quanh. –

+0

@DavidThornley: Tôi nghĩ câu hỏi là liệu thực hành tốt có dựa vào hành vi đó hay không. Suy nghĩ cá nhân của tôi là sẽ ổn khi dựa vào hành vi như vậy nếu và chỉ khi một người sử dụng các dự đoán cứng nhắc mỗi khi một người muốn thực thi nó. Nếu 'a' và' b' là ví dụ:'UInt32', biểu thức' (UInt32) (a-b) 'sẽ tạo ra hành vi gói, và nó sẽ rõ ràng rằng hành vi như vậy được mong đợi. Kỳ vọng rằng (a-b) là nghĩa vụ mang lại hành vi gói, tuy nhiên, là ít rõ ràng hơn, và trên máy nơi 'int' là lớn hơn 32 bit, nó có thể trong thực tế * không * mang lại hành vi như vậy. – supercat

1

tôi sẽ không dựa vào nó chỉ vì những lý do dễ đọc. Bạn sẽ gỡ lỗi mã của mình trong nhiều giờ trước khi bạn tìm ra nơi bạn đang đặt lại biến đó thành 0.

+0

Không thực sự, miễn là hành vi được ghi lại và nhận xét. Bên cạnh đó, có những lý do chính đáng cho (un) tràn số nguyên đã ký, ví dụ trong dấu thời gian cố định chiều rộng, nơi độ chính xác quan trọng hơn và tràn có thể được phát hiện hợp lý. –

0

Vì số đã ký trên CPU có thể được biểu diễn theo các cách khác nhau, 99.999% của tất cả các CPU hiện tại sử dụng ký hiệu bổ sung twos. Vì đây là phần lớn các máy trên mạng, rất khó để tìm ra một hành vi khác nhau mặc dù trình biên dịch có thể kiểm tra nó (cơ hội chất béo). Các thông số kỹ thuật C tuy nhiên phải chiếm 100% các trình biên dịch vì vậy đã không xác định hành vi của nó.

Vì vậy, điều này sẽ khiến mọi thứ trở nên rối rắm hơn, đó là lý do chính đáng để tránh điều đó. Tuy nhiên, nếu bạn có một lý do thực sự tốt (nói, tăng hiệu suất của yếu tố 3 cho phần quan trọng của mã), sau đó tài liệu nó tốt và sử dụng nó.

+2

Bạn đang đề cập đến các số nguyên đã ký. Tiêu chuẩn C ghi lại hành vi của các số nguyên không dấu và đó là câu hỏi. –

2

Tôi thường xuyên sử dụng để biết liệu đã đến lúc phải làm gì đó chưa.

UInt32 now = GetCurrentTime() 

if(now - then > 100) 
{ 
    // do something 
} 

Miễn là bạn kiểm tra giá trị trước 'bây giờ' vòng 'thì', bạn sẽ ổn cho tất cả các giá trị 'bây giờ' và 'sau đó'.

EDIT: Tôi đoán đây thực sự là một phần phụ.

+0

Tôi tin rằng mã trên sẽ thất bại trên các hệ thống trong đó 'int' là 64 bit, vì toán hạng phép trừ sẽ được kéo dài đến số nguyên 64 bit, vì vậy nếu 'now' là 0 và' then' là 4294967295u (0xFFFFFFFF) kết quả của phép trừ sẽ không là 1 nhưng -4294967295. Đúc kết quả phép trừ vào UInt32 trước khi so sánh sẽ tránh được vấn đề đó vì (UInt32) (- 4294967295) sẽ là 1. – supercat

1

nơi Một nơi tràn unsigned thể được sử dụng một cách hữu ích là khi bạn phải lặp ngược từ một loại unsigned đưa ra:

void DownFrom(unsigned n) 
{ 
    unsigned m; 

    for(m = n; m != (unsigned)-1; --m) 
    { 
     DoSomething(m); 
    } 
} 

lựa chọn thay thế khác không phải là gọn gàng. Cố gắng làm m >= 0 không hoạt động trừ khi bạn thay đổi m thành chữ ký, nhưng sau đó bạn có thể cắt bớt giá trị của n - hoặc tệ hơn - chuyển đổi nó thành một số âm khi khởi tạo.

Nếu không, bạn phải làm! = 0 hoặc> 0 và sau đó thực hiện thủ công trường hợp 0 ​​sau vòng lặp.

+1

cho (m = n; m

+2

Hoặc "do {DoSomething (n);} trong khi (n--! = 0); ". Với "if (n! = (Unsigned) -1)" khi bắt đầu, nếu thật sự mong muốn -1 là giá trị đầu vào ma thuật có nghĩa là "không làm gì", vì nó nằm trong đoạn mã trên. –

+0

@Niki: không hoạt động, vòng lặp của bạn không làm gì vì m

3

Để đặt nó trong thời gian ngắn:

Đó là hoàn toàn hợp pháp/OK/an toàn để sử dụng unsigned tràn số nguyên như bạn thấy phù hợp miễn là bạn chú ý và tuân thủ các định nghĩa (đối với bất cứ mục đích - tối ưu hóa, siêu các thuật toán thông minh, v.v.)

0

Sẽ ổn khi dựa vào tràn miễn là bạn biết KHI nó sẽ xảy ra ...

Tôi, ví dụ, đã gặp sự cố khi triển khai C MD5 khi di chuyển sang trình biên dịch mới hơn ... Mã đã mong đợi tràn nhưng cũng dự kiến ​​32 bit ints.

Với 64 bit kết quả sai!

May mắn thay đó là những thử nghiệm tự động cho: Tôi bắt gặp vấn đề sớm nhưng đây có thể là một câu chuyện kinh dị thực sự nếu không được chú ý.

Bạn có thể tranh luận "nhưng điều này hiếm khi xảy ra": có nhưng đó là điều khiến nó trở nên nguy hiểm hơn! Khi có lỗi, mọi người đều nghi ngờ mã được viết trong vài ngày qua. Không ai là mã f đáng ngờ "chỉ hoạt động trong nhiều năm" và thường không ai biết cách hoạt động ...

+0

Đây là trường hợp xác nhận thời gian biên dịch để kiểm tra giả định rằng sizeof (int) == 4 là hợp lệ sẽ là một điều tốt. Điều đó có thể đã ném một lỗi ngay cả trước khi các bài kiểm tra được chạy. – RBerteig

3

Chỉ vì bạn biết minutiae của tiêu chuẩn không có nghĩa là người duy trì mã của bạn. Người đó có thể phải lãng phí thời gian lo lắng về điều này trong khi gỡ lỗi sau này, hoặc phải tìm kiếm tiêu chuẩn để xác minh hành vi này sau này. Chắc chắn, chúng tôi mong đợi sự thành thạo với các tính năng hợp lý của một ngôn ngữ trong một lập trình viên làm việc - và các công ty/nhóm khác nhau có một kỳ vọng khác nhau về mức độ thành thạo hợp lý đó là ở đâu. Nhưng đối với hầu hết các nhóm, điều này có vẻ hơi nhiều để mong đợi người tiếp theo biết được điều đó và không phải nghĩ về điều đó.

Nếu điều đó không đủ, bạn có nhiều khả năng gặp phải lỗi trình biên dịch khi bạn làm việc xung quanh các cạnh của tiêu chuẩn. Hoặc tệ hơn, người chuyển mã này sang một nền tảng mới có thể chạy vào chúng.

Tóm lại, tôi bỏ phiếu không làm điều đó!

0

Nếu bạn sử dụng nó một cách khôn ngoan (được nhận xét và dễ đọc), bạn có thể hưởng lợi từ nó bằng cách có mã nhỏ hơn và nhanh hơn.

0

siukurnin làm cho một điểm tốt, mà bạn cần biết khi tràn sẽ xảy ra. Cách dễ nhất để tránh vấn đề về tính di động mà ông mô tả là sử dụng các kiểu số nguyên cố định chiều rộng từ stdint.h. uint32_t là một số nguyên 32 bit không dấu trên tất cả các nền tảng và hệ điều hành và sẽ không hoạt động khác khi được biên dịch cho một hệ thống khác.

0

Tôi khuyên bạn nên luôn có một dàn diễn viên rõ ràng bất cứ lúc nào người ta sẽ dựa vào các số không dấu. Nếu không thì có thể có bất ngờ. Ví dụ: nếu "int" là 64 bit, mã như sau:

UInt32 foo,bar; 
if ((foo-bar) < 100) // Report if foo is between bar and bar+99, inclusive) 
    ... do something 

có thể không thành công, vì "foo" và "bar" sẽ được thăng cấp thành số nguyên 64 bit. Thêm một typecast trở lại UInt32 trước khi kiểm tra kết quả chống lại 100 sẽ ngăn chặn các vấn đề trong trường hợp đó.

Ngẫu nhiên, tôi tin rằng cách di động duy nhất để trực tiếp nhận được 32 bit dưới cùng của sản phẩm của hai UInt32 là truyền một trong các int đến UInt64 trước khi thực hiện nhân. Nếu không, UInt32 có thể được chuyển đổi thành Int64's đã ký, với phép nhân bị tràn và sinh ra kết quả không xác định.

Các vấn đề liên quan