Cảnh báo - so sánh giữa các biểu thức số nguyên có dấu và không dấu


80

Tôi hiện đang làm việc với Accelerated C ++ và đã gặp sự cố trong bài tập 2-3.

Tổng quan nhanh về chương trình - về cơ bản chương trình lấy một tên, sau đó hiển thị lời chào trong khung dấu hoa thị - tức là Xin chào! được bao quanh bởi * 's.

Bài tập - Trong chương trình ví dụ, tác giả sử dụng const intđể xác định khoảng trống (khoảng trắng) giữa lời chào và dấu sao. Sau đó, họ yêu cầu người đọc, như một phần của bài tập, yêu cầu người dùng cung cấp thông tin đầu vào về mức độ họ muốn đệm.

Tất cả điều này có vẻ dễ dàng, tôi tiếp tục yêu cầu người dùng cho hai số nguyên ( int) và lưu trữ chúng và thay đổi chương trình để sử dụng các số nguyên này, xóa những số nguyên được tác giả sử dụng, khi biên dịch mặc dù tôi nhận được cảnh báo sau;

Bài tập2-3.cpp: 46: cảnh báo: so sánh giữa biểu thức số nguyên có dấu và không dấu

Sau một số nghiên cứu, nó có vẻ là do mã cố gắng so sánh một trong các số nguyên ở trên ( int) với a string::size_type, điều này tốt. Nhưng tôi đã tự hỏi - điều này có nghĩa là tôi nên thay đổi một trong các số nguyên thành unsigned int? Điều quan trọng là phải trình bày rõ ràng liệu các số nguyên của tôi có dấu hay không dấu?

 cout << "Please enter the size of the frame between top and bottom you would like ";
 int padtopbottom;
 cin >> padtopbottom;

 cout << "Please enter size of the frame from each side you would like: ";
 unsigned int padsides; 
 cin >> padsides;

 string::size_type c = 0; // definition of c in the program
 if (r == padtopbottom + 1 && c == padsides + 1) { // where the error occurs

Trên đây là các bit mã liên quan, cthuộc loại string::size_typevì chúng tôi không biết lời chào có thể dài bao nhiêu - nhưng tại sao tôi gặp sự cố này bây giờ, khi mã của tác giả không gặp sự cố khi sử dụng const int? Ngoài ra - đối với bất kỳ ai có thể đã hoàn thành Accelerated C ++ - điều này sẽ được giải thích ở phần sau của cuốn sách?

Tôi đang sử dụng Linux Mint bằng cách sử dụng g ++ thông qua Geany, nếu điều đó giúp ích hoặc tạo ra sự khác biệt (như tôi đã đọc rằng nó có thể khi xác định điều gì string::size_typelà).


2
Sẽ không ai cho rằng bạn vẫn muốn ký số nguyên? Tôi không thể nghĩ ra một lý do hợp lý tại sao phía trên và phía dưới nên tiêu cực
Woot4Moo

Điều này đúng và tôi đã đề cập điều này trong bài đăng ở trên, nhưng tôi vẫn không hiểu tại sao vấn đề này không xảy ra trong chương trình ví dụ của tác giả khi họ sử dụng const int? Tôi chắc chắn rằng tôi sẽ hiểu được điều đó trong cuốn sách, nhưng không thể không tò mò.
Tim Harrington

Scrap đó - rõ ràng là nó không đưa ra cảnh báo trong tình huống đó bởi vì int luôn luôn là 1 ... oops.
Tim Harrington

1
Nói chung, sự gia tăng phạm vi không đáng để gặp rắc rối khi sử dụng unsignedcác loại tích phân cho số đếm. Các số không có dấu cũng có hành vi bao quanh được đảm bảo, làm cho chúng kém hiệu quả hơn một chút.
Jon Purdy

4
Tác giả có thể đã nhìn thấy cảnh báo tương tự, và chỉ bỏ qua nó. Đừng cho rằng tác giả của những cuốn sách là những người hiểu biết hoặc cẩn thận hơn bất kỳ lập trình viên bình thường nào.
Kristopher Johnson

Câu trả lời:


104

Thông thường nên khai báo các biến như unsignedhoặc size_tnếu chúng sẽ được so sánh với kích thước, để tránh vấn đề này. Bất cứ khi nào có thể, hãy sử dụng loại chính xác mà bạn sẽ so sánh với (ví dụ: sử dụng std::string::size_typekhi so sánh với std::stringđộ dài của a).

Các trình biên dịch đưa ra cảnh báo về việc so sánh các kiểu có dấu và chưa dấu vì phạm vi của các int có dấu và chưa dấu là khác nhau và khi chúng được so sánh với nhau, kết quả có thể gây ngạc nhiên. Nếu bạn phải thực hiện so sánh như vậy, bạn nên chuyển đổi rõ ràng một trong các giá trị sang một loại tương thích với loại khác, có lẽ sau khi kiểm tra để đảm bảo rằng chuyển đổi là hợp lệ. Ví dụ:

unsigned u = GetSomeUnsignedValue();
int i = GetSomeSignedValue();

if (i >= 0)
{
    // i is nonnegative, so it is safe to cast to unsigned value
    if ((unsigned)i >= u)
        iIsGreaterThanOrEqualToU();
    else
        iIsLessThanU();
}
else
{
    iIsNegative();
}

11
Tôi biết rằng tiêu chuẩn C hiện tại đôi khi yêu cầu các giá trị có dấu phủ định so sánh lớn hơn các giá trị không có dấu, nhưng có nên bất kỳ tình huống nào xảy ra không được coi là không được chấp nhận không? Tôi muốn thấy các tiêu chuẩn phát triển để ít nhất cho phép các trình biên dịch tạo ra hành vi chính xác về mặt số học (nghĩa là nếu giá trị có dấu là âm, nó sẽ so sánh nhỏ hơn và nếu giá trị không dấu vượt quá giá trị tối đa của kiểu có dấu, nó sẽ so sánh lớn hơn ). Có vẻ kỳ lạ là các trình biên dịch được yêu cầu để tạo ra hành vi ngốc nghếch khi không có các định dạng rõ ràng.
supercat

4
@supercat: Vì các phép so sánh số nguyên biên dịch thành một lệnh máy duy nhất và bất kỳ thử nghiệm hoặc xử lý trường hợp cạnh nào sẽ cần một số hướng dẫn máy, những gì bạn đề xuất không có khả năng được thêm vào dưới dạng tính năng C ... nó chắc chắn không thể là hành vi mặc định, vì nó sẽ giết chết hiệu suất một cách không cần thiết ngay cả khi lập trình viên biết nó không cần thiết.
Blake Miller

@BlakeMiller: Mã muốn so sánh giá trị có dấu và chưa dấu như thể cả hai đều không dấu có thể ép một và chạy "tốc độ tối đa". Nếu không, trong nhiều trường hợp, sự khác biệt sẽ là giữa so sánh và nhảy lấy hai hướng dẫn so với ba, sẽ rẻ hơn so với mã xử lý thủ công các trường hợp khác nhau.
supercat

1
@BlakeMiller: (Lý do tôi nói hai so với ba là hầu hết mã so sánh hai số sẽ sử dụng một lệnh để thực hiện so sánh và đặt cờ dựa trên chúng; trong nhiều trường hợp, trình biên dịch có thể sắp xếp mọi thứ để trước khi so sánh, cờ "dấu hiệu" sẽ giữ bit trên của một trong các toán hạng, do đó, một bước nhảy có điều kiện trước phép so sánh sẽ đủ để đảm bảo ngữ nghĩa chính xác). Lưu ý rằng vì có nhiều cách khác nhau để đạt được ngữ nghĩa chính xác, trình biên dịch có thể chọn bất kỳ cách nào có thể được thực hiện với giá rẻ nhất. Viết mã C cho đúng ngữ nghĩa sẽ khó hơn.
supercat

6
Chỉ để chứng minh rằng "kết quả có thể đáng ngạc nhiên", chương trình sau (sau khi chèn #include <cstdio>ở trên cùng ... và tôi đang sử dụng g ++ 4.4.7), sẽ in "true", nói rằng đúng là (đã ký) -1 là lớn hơn (unsigned) 12: int main(int, char**) { int x = -1; unsigned int y = 12; printf("x > y: %s\n", x > y ? "true":"false"); return 0; }
villapx

9

Tôi đã gặp vấn đề chính xác ngày hôm qua khi giải quyết vấn đề 2-3 trong Accelerated C ++. Chìa khóa là thay đổi tất cả các biến mà bạn sẽ so sánh (sử dụng toán tử Boolean) thành các kiểu tương thích. Trong trường hợp này, điều đó có nghĩa là string::size_type(hoặc unsigned int, nhưng vì ví dụ này đang sử dụng cái trước, tôi sẽ chỉ gắn bó với cái đó mặc dù cả hai tương thích về mặt kỹ thuật).

Lưu ý rằng trong mã ban đầu của họ, họ đã làm chính xác điều này cho bộ đếm c (trang 30 trong Phần 2.5 của cuốn sách), như bạn đã chỉ ra đúng.

Điều làm cho ví dụ này phức tạp hơn là các biến padding khác nhau (padidesides và padtopbottom), cũng như tất cả các bộ đếm, cũng phải được thay đổi thành string::size_type.

Đến ví dụ của bạn, mã mà bạn đã đăng sẽ có kết quả như sau:

cout << "Please enter the size of the frame between top and bottom";
string::size_type padtopbottom;
cin >> padtopbottom;

cout << "Please enter size of the frame from each side you would like: ";
string::size_type padsides; 
cin >> padsides;

string::size_type c = 0; // definition of c in the program

if (r == padtopbottom + 1 && c == padsides + 1) { // where the error no longer occurs

Lưu ý rằng trong điều kiện trước, bạn sẽ gặp lỗi nếu bạn không khởi tạo biến r dưới dạng a string::size_typetrong forvòng lặp. Vì vậy, bạn cần khởi tạo vòng lặp for bằng cách sử dụng một cái gì đó như:

    for (string::size_type r=0; r!=rows; ++r)   //If r and rows are string::size_type, no error!

Vì vậy, về cơ bản, khi bạn đưa một string::size_typebiến vào hỗn hợp, bất kỳ lúc nào bạn muốn thực hiện thao tác boolean trên mục đó, tất cả các toán hạng phải có kiểu tương thích để nó biên dịch mà không có cảnh báo.


6

Sự khác biệt quan trọng giữa int có dấu và không dấu là cách giải thích bit cuối cùng. Bit cuối cùng trong các kiểu có dấu đại diện cho dấu của số, nghĩa là: ví dụ:

0001 là 1 có dấu và không dấu 1001 là -1 có dấu và 9 không dấu

(Tôi đã tránh toàn bộ vấn đề bổ sung để giải thích rõ ràng! Đây không phải là chính xác cách các int được biểu diễn trong bộ nhớ!)

Bạn có thể tưởng tượng rằng nó tạo ra sự khác biệt nếu bạn so sánh với -1 hay với +9. Trong nhiều trường hợp, các lập trình viên quá lười biếng khi khai báo việc đếm số int là không có dấu (mở rộng đầu vòng lặp for) Đây thường không phải là vấn đề vì với số nguyên, bạn phải đếm đến 2 ^ 31 cho đến khi bit dấu của bạn cắn bạn. Đó là lý do tại sao nó chỉ là một cảnh báo. Vì chúng ta quá lười biếng để viết 'unsigned' thay vì 'int'.


Ah, tôi hiểu rồi - tôi đã thay đổi số đếm int thành unsigned. Đây được coi là thực hành tốt hay thậm chí là thực hành xấu? :)
Tim Harrington

Xin vui lòng, nếu bạn không tán thành, hãy giải thích lý do ngay sau đây. Ngay cả khi nó chỉ là một từ. Tôi không thể thấy câu trả lời của mình có sai sót gì. Bạn có thể giúp tôi với.
AndreasT

1
@Tim: "unsigned" là từ đồng nghĩa với "unsigned int". Bạn nên sử dụng int unsigned hoặc kiểu biến đếm / lặp tiêu chuẩn stl std :: size_t (cũng là một từ đồng nghĩa). Cách tốt nhất là sử dụng unsigned trong mọi trường hợp "lặp qua các phần tử từ 0 đến n". Nó cải thiện độ rõ ràng và loại bỏ các cảnh báo, vì vậy nó là người chiến thắng ;-)
AndreasT

9
Biểu diễn bên trong của các số nguyên có dấu là phụ thuộc trình biên dịch- (tức là máy-). Ký hiệu của bạn với một bit dấu không được sử dụng rộng rãi do một số vấn đề (+/- không là một trong số đó). Hầu hết các máy sử dụng khái niệm phần bù của hai để biểu diễn số âm. Ưu điểm là số học bình thường (không dấu) cũng có thể được sử dụng với bất kỳ thay đổi nào. Ý niệm bổ sung của -1 trong 2 sẽ là 1111 btw.
sstn

1
@AndreasT: mặc dù có thể hiểu là "tránh toàn bộ vấn đề bổ sung cho rõ ràng", bạn có thể sử dụng một ví dụ tương thích với bổ sung của 2, biểu diễn được sử dụng bởi hầu như tất cả các nền tảng. 1001cho -1 là một lựa chọn tồi, một lựa chọn tốt hơn nhiều sẽ là " 1111bằng -1 có dấu và 15 không dấu"
MestreLion

4

Ở các phạm vi cực hạn, một int không dấu có thể trở nên lớn hơn một int.
Do đó, trình biên dịch tạo ra một cảnh báo. Nếu bạn chắc chắn rằng đây không phải là vấn đề, hãy ép kiểu sang cùng một kiểu để cảnh báo biến mất (sử dụng C ++ ép kiểu để chúng dễ phát hiện).

Ngoài ra, hãy đặt các biến cùng kiểu để ngăn trình biên dịch phàn nàn.
Ý tôi là, liệu có khả năng có đệm âm không? Nếu vậy thì hãy giữ nó dưới dạng int. Nếu không, bạn có thể nên sử dụng int unsigned và để luồng phát hiện các trường hợp người dùng nhập vào một số âm.


0

hoặc sử dụng thư viện tiêu đề này và viết:

// |notEqaul|less|lessEqual|greater|greaterEqual
if(sweet::equal(valueA,valueB))

và không quan tâm đến các kích thước có dấu / chưa dấu hoặc các kích thước khác nhau


0

Vấn đề chính là phần cứng cơ bản, CPU, chỉ có hướng dẫn để so sánh hai giá trị có dấu hoặc so sánh hai giá trị chưa được ký. Nếu bạn chuyển hướng dẫn so sánh không dấu một giá trị âm, có dấu, nó sẽ coi nó như một số dương lớn. Vì vậy, -1, mẫu bit với tất cả các bit trên (bổ sung hai phần), trở thành giá trị không dấu lớn nhất cho cùng một số bit.

8-bit: -1 có dấu là các bit giống như 255 không dấu 16-bit: -1 có dấu là các bit giống như 65535 không dấu, v.v.

Vì vậy, nếu bạn có mã sau:

int fd;
fd = open( .... );

int cnt;
SomeType buf;

cnt = read( fd, &buf, sizeof(buf) );

if( cnt < sizeof(buf) ) {
    perror("read error");
}

bạn sẽ thấy rằng nếu lệnh đọc (2) không thành công do bộ mô tả tệp không hợp lệ (hoặc một số lỗi khác), cnt đó sẽ được đặt thành -1. Khi so sánh với sizeof (buf), một giá trị không dấu, câu lệnh if () sẽ sai vì 0xffffffff không nhỏ hơn cấu trúc dữ liệu sizeof () một số (hợp lý, không được kết hợp thành kích thước tối đa).

Vì vậy, bạn phải viết ở trên nếu, để loại bỏ cảnh báo có dấu / không dấu là:

if( cnt < 0 || (size_t)cnt < sizeof(buf) ) {
    perror("read error");
}

Điều này chỉ nói lên các vấn đề.

1.  Introduction of size_t and other datatypes was crafted to mostly work, 
    not engineered, with language changes, to be explicitly robust and 
    fool proof.
2.  Overall, C/C++ data types should just be signed, as Java correctly
    implemented.

Nếu bạn có các giá trị lớn đến mức bạn không thể tìm thấy kiểu giá trị có dấu hoạt động, thì bạn đang sử dụng bộ xử lý quá nhỏ hoặc quá lớn về cường độ giá trị trong ngôn ngữ bạn chọn. Nếu, giống như tiền, mọi chữ số đều có giá trị, thì có những hệ thống để sử dụng trong hầu hết các ngôn ngữ cung cấp cho bạn độ chính xác vô hạn. C / C ++ không làm tốt điều này và bạn phải rất rõ ràng về mọi thứ xung quanh các loại như đã đề cập trong nhiều câu trả lời khác ở đây.

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.