Đã ký chuyển đổi không dấu trong C - có luôn an toàn không?


135

Giả sử tôi có mã C sau đây.

unsigned int u = 1234;
int i = -5678;

unsigned int result = u + i;

Những chuyển đổi ngầm định nào đang diễn ra ở đây và mã này có an toàn cho tất cả các giá trị của uikhông? (An toàn, theo nghĩa là mặc dù kết quả trong ví dụ này sẽ tràn đến một số dương rất lớn, tôi có thể đưa nó trở lại int và nhận được kết quả thực sự.)

Câu trả lời:


223

Câu trả lời ngắn

Của bạn isẽ được chuyển đổi thành một số nguyên không dấu bằng cách thêm UINT_MAX + 1, sau đó việc bổ sung sẽ được thực hiện với các giá trị không dấu, dẫn đến một số lớn result(tùy thuộc vào các giá trị của ui).

Câu trả lời dài

Theo tiêu chuẩn C99:

6.3.1.8 Chuyển đổi số học thông thường

  1. Nếu cả hai toán hạng có cùng loại, thì không cần chuyển đổi thêm.
  2. Mặt khác, nếu cả hai toán hạng đã ký kiểu số nguyên hoặc cả hai đều có kiểu số nguyên không dấu, toán hạng có loại xếp hạng chuyển đổi số nguyên nhỏ hơn được chuyển đổi thành loại toán hạng có thứ hạng lớn hơn.
  3. Mặt khác, nếu toán hạng có loại số nguyên không dấu có thứ hạng lớn hơn hoặc bằng thứ hạng của loại toán hạng khác, thì toán hạng có loại số nguyên đã ký được chuyển đổi thành loại toán hạng có loại số nguyên không dấu.
  4. Mặt khác, nếu loại toán hạng có loại số nguyên đã ký có thể biểu thị tất cả các giá trị của loại toán hạng có loại số nguyên không dấu, thì toán hạng có loại số nguyên không dấu được chuyển đổi thành loại toán hạng có loại số nguyên đã ký.
  5. Mặt khác, cả hai toán hạng được chuyển đổi thành kiểu số nguyên không dấu tương ứng với loại toán hạng có kiểu số nguyên đã ký.

Trong trường hợp của bạn, chúng tôi có một int ( u) và uns int ( ) đã ký i. Đề cập đến (3) ở trên, vì cả hai toán hạng có cùng cấp bậc, bạn isẽ cần phải được chuyển đổi thành một số nguyên không dấu.

6.3.1.3 Số nguyên đã ký và không dấu

  1. Khi một giá trị với loại số nguyên được chuyển đổi sang loại số nguyên khác ngoài _Bool, nếu giá trị có thể được biểu thị bằng loại mới, nó không thay đổi.
  2. Mặt khác, nếu loại mới không được ký, giá trị được chuyển đổi bằng cách lặp lại hoặc trừ đi nhiều hơn một giá trị tối đa có thể được biểu thị trong loại mới cho đến khi giá trị nằm trong phạm vi của loại mới.
  3. Mặt khác, loại mới được ký và giá trị không thể được biểu diễn trong đó; hoặc kết quả là xác định thực hiện hoặc tín hiệu xác định thực hiện được đưa ra.

Bây giờ chúng ta cần tham khảo (2) ở trên. Của bạn isẽ được chuyển đổi thành một giá trị không dấu bằng cách thêm UINT_MAX + 1. Vì vậy, kết quả sẽ phụ thuộc vào cách UINT_MAXxác định trong việc thực hiện của bạn. Nó sẽ lớn, nhưng nó sẽ không tràn, bởi vì:

6.2.5 (9)

Một tính toán liên quan đến toán hạng không dấu không bao giờ có thể tràn, vì một kết quả không thể được biểu thị bằng kiểu số nguyên không dấu kết quả được giảm modulo số lớn hơn một giá trị lớn nhất có thể được biểu thị bằng loại kết quả.

Tiền thưởng: Chuyển đổi số học Semi-WTF

#include <stdio.h>

int main(void)
{
  unsigned int plus_one = 1;
  int minus_one = -1;

  if(plus_one < minus_one)
    printf("1 < -1");
  else
    printf("boring");

  return 0;
}

Bạn có thể sử dụng liên kết này để thử trực tuyến này: https://repl.it/repls/QuickWhimsicalBytes

Tiền thưởng: Hiệu ứng phụ chuyển đổi số học

Các quy tắc chuyển đổi số học có thể được sử dụng để lấy giá trị UINT_MAXbằng cách khởi tạo một giá trị không dấu đến -1, nghĩa là:

unsigned int umax = -1; // umax set to UINT_MAX

Điều này được đảm bảo là di động bất kể đại diện số đã ký của hệ thống vì các quy tắc chuyển đổi được mô tả ở trên. Xem câu hỏi SO này để biết thêm thông tin: Có an toàn khi sử dụng -1 để đặt tất cả các bit thành đúng không?


Tôi không hiểu tại sao nó không thể đơn giản làm một giá trị tuyệt đối và sau đó coi là không dấu, giống như với các số dương?
Jose Salvatierra

7
@ D.Singh bạn có thể vui lòng chỉ ra những phần sai trong câu trả lời không?
Shmil Con mèo

Để chuyển đổi được ký thành không dấu, chúng tôi thêm giá trị tối đa của giá trị không dấu (UINT_MAX +1). Tương tự, cách dễ dàng để chuyển đổi từ không dấu sang đã ký là gì? Chúng ta có cần trừ số đã cho khỏi giá trị tối đa (256 trong trường hợp ký tự không dấu) không? Ví dụ: 140 khi được chuyển đổi thành số đã ký trở thành -116. Nhưng 20 trở thành 20 chính nó. Vậy có mẹo nào dễ ở đây không?
Jon Wheelock


24

Chuyển đổi từ đã ký thành không dấu không nhất thiết chỉ là sao chép hoặc diễn giải lại việc thể hiện giá trị đã ký. Trích dẫn tiêu chuẩn C (C99 6.3.1.3):

Khi một giá trị với loại số nguyên được chuyển đổi sang loại số nguyên khác ngoài _Bool, nếu giá trị có thể được biểu thị bằng loại mới, nó không thay đổi.

Mặt khác, nếu loại mới không được ký, giá trị được chuyển đổi bằng cách lặp lại hoặc trừ đi nhiều hơn một giá trị tối đa có thể được biểu thị trong loại mới cho đến khi giá trị nằm trong phạm vi của loại mới.

Mặt khác, loại mới được ký và giá trị không thể được biểu diễn trong đó; hoặc kết quả là xác định thực hiện hoặc tín hiệu xác định thực hiện được đưa ra.

Đối với đại diện bổ sung của hai ngày gần như phổ biến ngày nay, các quy tắc tương ứng với việc diễn giải lại các bit. Nhưng đối với các đại diện khác (ký hiệu và cường độ hoặc bổ sung của một), việc triển khai C vẫn phải sắp xếp cho cùng một kết quả, điều đó có nghĩa là chuyển đổi không thể chỉ sao chép các bit. Ví dụ: (không dấu) -1 == UINT_MAX, bất kể đại diện.

Nói chung, chuyển đổi trong C được xác định để hoạt động trên các giá trị, không phải trên các đại diện.

Để trả lời câu hỏi ban đầu:

unsigned int u = 1234;
int i = -5678;

unsigned int result = u + i;

Giá trị của i được chuyển đổi thành int unsign, năng suất UINT_MAX + 1 - 5678. Giá trị này sau đó được thêm vào giá trị không dấu 1234, mang lại UINT_MAX + 1 - 4444.

(Không giống như tràn không dấu, tràn tràn đã ký sẽ gọi hành vi không xác định. Wraparound là phổ biến, nhưng không được đảm bảo bởi tiêu chuẩn C - và tối ưu hóa trình biên dịch có thể phá hoại mã mà đưa ra các giả định không chính đáng.)


5

Nhắc đến kinh thánh :

  • Hoạt động bổ sung của bạn làm cho int được chuyển đổi thành int không dấu.
  • Giả sử đại diện bổ sung của hai loại và các loại có kích thước bằng nhau, mẫu bit không thay đổi.
  • Chuyển đổi từ int unsign sang int đã ký là phụ thuộc thực hiện. (Nhưng nó có thể hoạt động theo cách bạn mong đợi trên hầu hết các nền tảng hiện nay.)
  • Các quy tắc phức tạp hơn một chút trong trường hợp kết hợp ký và không dấu với các kích cỡ khác nhau.

3

Khi một biến không dấu và một biến được ký được thêm vào (hoặc bất kỳ hoạt động nhị phân nào), cả hai đều được chuyển đổi hoàn toàn thành không dấu, trong trường hợp này sẽ dẫn đến một kết quả rất lớn.

Vì vậy, nó an toàn theo nghĩa là kết quả có thể rất lớn và sai, nhưng nó sẽ không bao giờ sụp đổ.


Không đúng. 6.3.1.8 Chuyển đổi số học thông thường Nếu bạn tính tổng một int và một char không dấu thì cái sau được chuyển đổi thành int. Nếu bạn tính tổng hai ký tự không dấu thì chúng được chuyển đổi thành int.
2501

3

Khi chuyển đổi từ đã ký sang không dấu, có hai khả năng. Các số ban đầu dương vẫn còn (hoặc được hiểu là) cùng một giá trị. Số ban đầu âm sẽ được hiểu là số dương lớn hơn.


1

Như đã được trả lời trước đó, bạn có thể bỏ qua lại giữa ký và không dấu mà không có vấn đề gì. Trường hợp đường viền cho số nguyên đã ký là -1 (0xFFFFFFFF). Hãy thử cộng và trừ từ đó và bạn sẽ thấy rằng bạn có thể bỏ lại và làm cho đúng.

Tuy nhiên, nếu bạn định truyền qua lại, tôi thực sự khuyên bạn nên đặt tên cho các biến của mình sao cho rõ ràng chúng thuộc loại nào, ví dụ:

int iValue, iResult;
unsigned int uValue, uResult;

Quá dễ dàng để bị phân tâm bởi các vấn đề quan trọng hơn và quên đi biến nào là loại nếu chúng được đặt tên mà không có gợi ý. Bạn không muốn truyền tới một dấu không dấu và sau đó sử dụng nó làm chỉ mục mảng.


0

Những gì chuyển đổi ngầm đang diễn ra ở đây,

tôi sẽ được chuyển đổi thành một số nguyên không dấu.

và mã này có an toàn cho tất cả các giá trị của u và i không?

An toàn theo nghĩa được xác định rõ có (xem https://stackoverflow.com/a/50632/5083516 ).

Các quy tắc được viết bằng chữ thường khó đọc tiêu chuẩn nhưng về cơ bản, bất kỳ biểu diễn nào được sử dụng trong số nguyên đã ký, số nguyên không dấu sẽ chứa đại diện bổ sung 2 của số.

Phép cộng, phép trừ và phép nhân sẽ hoạt động chính xác trên các số này dẫn đến một số nguyên không dấu khác chứa số bổ sung twos biểu thị "kết quả thực".

phân chia và truyền cho các loại số nguyên không dấu lớn hơn sẽ có kết quả được xác định rõ nhưng những kết quả đó sẽ không phải là biểu diễn bổ sung của 2 "kết quả thực".

(An toàn, theo nghĩa là mặc dù kết quả trong ví dụ này sẽ tràn đến một số dương rất lớn, tôi có thể chuyển nó trở lại int và nhận được kết quả thực sự.)

Mặc dù các chuyển đổi từ được ký thành không dấu được xác định theo tiêu chuẩn, ngược lại được xác định theo thực thi cả gcc và msvc xác định chuyển đổi sao cho bạn sẽ nhận được "kết quả thực" khi chuyển đổi số bổ sung 2 được lưu trong số nguyên không dấu trở lại thành số nguyên đã ký . Tôi hy vọng bạn sẽ chỉ tìm thấy bất kỳ hành vi nào khác trên các hệ thống tối nghĩa không sử dụng phần bù 2 cho các số nguyên đã ký.

https://gcc.gnu.org/onlinesocs/gcc/Integers-im THỰCation.html # integers-im THỰCation https://msdn.microsoft.com/en-us/l Library / 0eex498h.aspx


-17

Câu trả lời khủng khiếp Galore

Ozgur Ozcitak

Khi bạn chuyển từ đã ký sang không dấu (và ngược lại), biểu diễn bên trong của số không thay đổi. Những thay đổi là cách trình biên dịch diễn giải bit dấu.

Điều này là hoàn toàn sai.

Thảm Fredriksson

Khi một biến không dấu và một biến được ký được thêm vào (hoặc bất kỳ hoạt động nhị phân nào), cả hai đều được chuyển đổi hoàn toàn thành không dấu, trong trường hợp này sẽ dẫn đến một kết quả rất lớn.

Điều này cũng sai. Số nguyên không được ký có thể được thăng cấp thành số nguyên nếu chúng có độ chính xác bằng nhau do các bit đệm trong loại không dấu.

smh

Hoạt động bổ sung của bạn làm cho int được chuyển đổi thành int không dấu.

Sai lầm. Có lẽ nó làm và có thể nó không.

Chuyển đổi từ int unsign sang int đã ký là phụ thuộc thực hiện. (Nhưng nó có thể hoạt động theo cách bạn mong đợi trên hầu hết các nền tảng hiện nay.)

Sai lầm. Đó là hành vi không xác định nếu nó gây ra tràn hoặc giá trị được bảo toàn.

Vô danh

Giá trị của i được chuyển đổi thành ...

Sai lầm. Phụ thuộc vào độ chính xác của một int so với int không dấu.

Giá Taylor

Như đã được trả lời trước đó, bạn có thể bỏ qua lại giữa ký và không dấu mà không có vấn đề gì.

Sai lầm. Cố gắng lưu trữ một giá trị bên ngoài phạm vi của một số nguyên đã ký dẫn đến hành vi không xác định.

Bây giờ tôi cuối cùng có thể trả lời câu hỏi.

Nếu độ chính xác của int bằng với int unsign, u sẽ được thăng cấp thành int đã ký và bạn sẽ nhận được giá trị -4444 từ biểu thức (u + i). Bây giờ, nếu bạn và tôi có các giá trị khác, bạn có thể có hành vi tràn và không xác định nhưng với những con số chính xác đó, bạn sẽ nhận được -4444 [1] . Giá trị này sẽ có kiểu int. Nhưng bạn đang cố lưu trữ giá trị đó vào một số nguyên không dấu để sau đó sẽ được chuyển thành một số nguyên không dấu và giá trị mà kết quả cuối cùng sẽ có là (UINT_MAX + 1) - 4444.

Nếu độ chính xác của int unsign lớn hơn int int, int int đã ký sẽ được thăng cấp thành int unsign mang lại giá trị (UINT_MAX + 1) - 5678 sẽ được thêm vào int 1234 không dấu khác. các giá trị khác, làm cho biểu thức nằm ngoài phạm vi {0..UINT_MAX}, giá trị (UINT_MAX + 1) sẽ được thêm hoặc trừ cho đến khi kết quả DOES nằm trong phạm vi {0..UINT_MAX) và sẽ không xảy ra hành vi không xác định nào .

Độ chính xác là gì?

Số nguyên có bit đệm, bit ký và bit giá trị. Số nguyên không dấu không có một dấu hiệu rõ ràng. Char chưa ký được đảm bảo hơn nữa để không có bit đệm. Số lượng bit giá trị mà một số nguyên có độ chính xác là bao nhiêu.

[Gotchas]

Không thể sử dụng macro sizeof macro để xác định độ chính xác của số nguyên nếu có các bit đệm. Và kích thước của một byte không phải là một octet (tám bit) như được định nghĩa bởi C99.

[1] Tràn có thể xảy ra tại một trong hai điểm. Hoặc trước khi bổ sung (trong thời gian khuyến mãi) - khi bạn có một số nguyên không dấu quá lớn để phù hợp với một số nguyên. Việc tràn cũng có thể xảy ra sau khi bổ sung ngay cả khi int unsign nằm trong phạm vi của int, sau khi bổ sung, kết quả vẫn có thể bị tràn.


6
"Số nguyên chưa ký có thể được thăng cấp lên số nguyên". Không đúng. Không có khuyến mãi số nguyên xảy ra vì các loại đã được xếp hạng> = int. 6.3.1.1: "Thứ hạng của bất kỳ loại số nguyên không dấu nào sẽ bằng thứ hạng của loại số nguyên đã ký tương ứng, nếu có." và 6.3.1.8: "Mặt khác, nếu toán hạng có loại số nguyên không dấu có thứ hạng lớn hơn hoặc bằng thứ hạng của loại toán hạng khác, thì toán hạng có loại số nguyên đã ký được chuyển đổi thành loại toán hạng có số nguyên không dấu kiểu." cả hai đảm bảo intđược chuyển đổi thành unsigned intkhi áp dụng chuyển đổi số học thông thường.
CB Bailey

1
6.3.1.8 Chỉ xảy ra sau khi khuyến mãi số nguyên. Đoạn mở đầu cho biết "Mặt khác, các chương trình khuyến mãi số nguyên được thực hiện trên cả hai toán hạng. THEN các quy tắc sau được áp dụng cho các toán hạng được thăng cấp". Vì vậy, hãy đọc các quy tắc quảng cáo 6.3.1.1 ... "Một đối tượng hoặc biểu thức có loại số nguyên có thứ hạng chuyển đổi số nguyên nhỏ hơn hoặc THIẾT BỊ cho thứ hạng của int và unsign int" và "Nếu một int có thể biểu thị tất cả các giá trị của loại ban đầu, giá trị được chuyển đổi thành một int ".
Elite Mx

1
6.3.1.1 Quảng cáo số nguyên được sử dụng để chuyển đổi một số loại số nguyên không inthoặc unsigned intsang một trong những loại có loại unsigned inthoặc loại intdự kiến. "Hoặc bằng" đã được thêm vào trong TC2 để cho phép liệt kê các loại xếp hạng chuyển đổi bằng inthoặc unsigned intđược chuyển đổi thành một trong những loại đó. Nó không bao giờ có ý định rằng chương trình khuyến mãi được mô tả sẽ chuyển đổi giữa unsigned intint. Việc xác định loại phổ biến giữa unsigned intintvẫn được điều chỉnh bởi 6.3.1.8, thậm chí là bài TC2.
CB Bailey

19
Đăng câu trả lời sai trong khi chỉ trích câu trả lời sai của người khác nghe có vẻ không phải là một chiến lược tốt để có được công việc ... ;-)
R .. GitHub DỪNG GIÚP ICE

6
Tôi không bỏ phiếu để xóa vì mức độ sai này kết hợp với sự kiêu ngạo là quá thú vị
MM
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.