답변 중 일부는 여기에 서명 부호없는 값 사이의 놀라운 프로모션 규칙을 언급하지만 더 많은 관련 문제처럼 보인다 혼합 서명과 서명되지 않은 값을, 왜 반드시 설명하지 않습니다 서명 변수가 선호 될 것이다 서명되지 않은 시나리오를 혼합의 외부.
내 경험상 혼합 비교와 승격 규칙 외에 부호없는 값이 다음과 같이 버그 자석 인 두 가지 주요 이유가 있습니다.
부호없는 값은 프로그래밍에서 가장 일반적인 값인 0에서 불연속성을 갖습니다.
부호없는 정수와 부호있는 정수는 모두 최소값과 최대 값에서 불연속성 을 가지며, 여기에서 줄 바꿈 (부호 없음)하거나 정의되지 않은 동작 (부호 있음)을 유발합니다. 들어 unsigned
이러한 점에있다 제로 와 UINT_MAX
. 들어 int
가에있다 INT_MIN
하고 INT_MAX
. 전형적인 값 INT_MIN
및 INT_MAX
4 바이트로 시스템 int
값은 다음 -2^31
과 2^31-1
, 이러한 시스템에서 UINT_MAX
일반적이다 2^32-1
.
unsigned
적용되지 않는 주요 버그 유발 문제 는 0에서 불연속성int
이 있다는 것 입니다. 물론 0은 1,2,3과 같은 다른 작은 값과 함께 프로그램에서 매우 일반적인 값입니다. 다양한 구조에서 작은 값, 특히 1을 더하고 빼는 것이 일반적이며, 값에서 어떤 것을 빼고 0이된다면 엄청난 양의 값과 거의 확실한 버그를 얻게됩니다.unsigned
마지막 0.5를 제외하고 인덱스별로 벡터의 모든 값을 반복하는 코드를 고려하십시오 .
for (size_t i = 0; i < v.size() - 1; i++) {
이것은 언젠가 빈 벡터를 전달할 때까지 잘 작동합니다. 0 반복을 수행하는 대신 v.size() - 1 == a giant number
1 을 얻고 40 억 반복을 수행하고 거의 버퍼 오버플로 취약성을 갖습니다.
다음과 같이 작성해야합니다.
for (size_t i = 0; i + 1 < v.size(); i++) {
따라서이 경우 "고정"될 수 있지만의 서명되지 않은 특성에 대해 신중하게 생각해야만합니다 size_t
. 때로는 상수 대신 적용하려는 변수 오프셋이 있기 때문에 위의 수정을 적용 할 수없는 경우도 있습니다. 이는 양수 또는 음수 일 수 있습니다. 따라서 비교해야하는 "측"은 부호에 따라 달라집니다. -이제 코드가 정말 지저분 해집니다.
0까지 반복하는 코드에도 비슷한 문제가 있습니다. 같은 것은 while (index-- > 0)
잘 작동하지만 분명히 동등한 while (--index >= 0)
것은 서명되지 않은 값으로 종료되지 않습니다. 컴파일러는 오른쪽이 리터럴 0 일 때 경고 할 수 있지만 런타임에 결정된 값이면 확실히 그렇지 않습니다.
대위법
어떤 사람들은 부호있는 값에도 두 개의 불연속성이 있다고 주장 할 수 있는데, 왜 unsigned를 선택합니까? 차이점은 두 불연속 모두 0에서 매우 (최대) 멀리 떨어져 있다는 것입니다. 저는 이것을 "오버플로"라는 별도의 문제로 생각합니다. 부호있는 값과 부호없는 값 모두 매우 큰 값에서 오버플로 될 수 있습니다. 많은 경우 값의 가능한 범위에 대한 제약으로 인해 오버플로가 불가능하고 많은 64 비트 값의 오버플로가 물리적으로 불가능할 수 있습니다. 가능하더라도 오버플로 관련 버그의 가능성은 "0에서"버그에 비해 종종 미미 하며 부호없는 값에 대해서도 오버플로가 발생합니다 . 따라서 unsigned는 두 세계의 최악을 결합합니다. 잠재적으로 매우 큰 크기 값으로 오버플로되고 0에서 불연속됩니다. 서명은 전자 만 있습니다.
많은 사람들이 서명하지 않은 상태에서 "당신은 조금 잃는다"고 주장 할 것입니다. 이것은 종종 사실이지만 항상 그런 것은 아닙니다 (부호없는 값 사이의 차이를 나타내야하는 경우 어쨌든 해당 비트를 잃게됩니다. 너무 많은 32 비트 항목이 어쨌든 2GiB로 제한되거나 이상한 회색 영역이 표시됩니다. 파일은 4GiB 일 수 있지만 두 번째 2GiB 절반에서는 특정 API를 사용할 수 없습니다.
unsigned가 당신을 조금 사주는 경우에도 : 그것은 당신을 많이 사주지 않습니다. 만약 당신이 20 억 개 이상의 "사물"을 지원해야한다면, 아마도 곧 40 억 개 이상을 지원해야 할 것입니다.
논리적으로 부호없는 값은 부호있는 값의 하위 집합입니다.
수학적으로 부호없는 값 (음이 아닌 정수)은 부호있는 정수 (_ 정수라고 함)의 하위 집합입니다. 2 . 그러나 부호있는 값 은 빼기와 같이 부호없는 값 에서만 연산에서 자연스럽게 튀어 나옵니다 . 부호없는 값은 빼기로 닫히지 않는다고 말할 수 있습니다 . 부호있는 값도 마찬가지입니다.
파일에있는 두 개의 서명되지 않은 인덱스 사이의 "델타"를 찾고 싶습니까? 뺄셈을 올바른 순서로하는 것이 좋습니다. 그렇지 않으면 잘못된 답을 얻을 수 있습니다. 물론 올바른 순서를 결정하기 위해 종종 런타임 검사가 필요합니다! 부호없는 값을 숫자로 처리 할 때 (논리적으로) 부호있는 값이 계속 표시되므로 부호있는 값으로 시작하는 것이 좋습니다.
대위법
위의 각주 (2)에서 언급했듯이 C ++의 부호있는 값은 실제로 동일한 크기의 부호없는 값의 하위 집합이 아니므로 부호없는 값은 부호있는 값과 동일한 수의 결과를 나타낼 수 있습니다.
사실이지만 범위가 덜 유용합니다. 뺄셈과 0 ~ 2N 범위의 부호없는 숫자와 -N ~ N 범위의 부호있는 숫자를 고려합니다. 임의의 뺄셈은 _ 두 경우 모두 -2N ~ 2N 범위의 결과를 가져 오며 두 가지 유형의 정수 모두 다음을 나타낼 수 있습니다. 그것의 절반. -N에서 N까지의 0을 중심으로하는 영역은 일반적으로 0에서 2N 범위보다 훨씬 더 유용합니다 (실제 코드에서 더 많은 실제 결과를 포함 함). 균일 (log, zipfian, normal 등) 이외의 일반적인 분포를 고려하고 해당 분포에서 무작위로 선택된 값을 빼는 것을 고려하십시오. [0, 2N]보다 더 많은 값이 [-N, N]으로 끝나는 방식 (실제로 결과 분포) 항상 0에 중심).
64 비트는 부호있는 값을 숫자로 사용하는 많은 이유에서 문을 닫습니다.
나는 인수가 위에서 이미 32 비트 값에 대한 강력한라고 생각하지만, 모두 서명하고 서로 다른 임계 값에 부호없는 영향을 미치는 오버 플로우의 경우는 않는다 "20 억은"많은 초과 할 수있는 숫자이기 때문에, 32 비트 값을 발생 추상 및 물리적 양 (수십억 달러, 수십억 나노초, 수십억 개의 요소가있는 어레이). 따라서 누군가가 unsigned 값에 대해 양의 범위를 두 배로 늘려서 충분히 확신한다면 오버플로가 중요하고 unsigned를 약간 선호하는 경우를 만들 수 있습니다.
특수 도메인 외부에서 64 비트 값은이 문제를 크게 제거합니다. 10 개 이상 - 64 비트 서명 된 값 9,223,372,036,854,775,807의 상부 범위가 quintillion를 . 그것은 많은 나노초 (약 292 년 가치)와 많은 돈입니다. 또한 오랜 시간 동안 일관된 주소 공간에 RAM이있는 컴퓨터보다 더 큰 어레이입니다. 그렇다면 아마 9 천경이면 모두에게 충분할까요 (현재로서는)?
부호없는 값을 사용하는 경우
스타일 가이드는 부호없는 숫자의 사용을 금지하거나 반드시 권장하지 않습니다. 결론은 다음과 같습니다.
부호없는 유형을 사용하여 변수가 음수가 아니라고 단언하지 마십시오.
실제로 부호없는 변수에 대한 좋은 용도가 있습니다.
N- 비트 수량을 정수가 아니라 단순히 "비트 모음"으로 처리하려는 경우. 예를 들어, 비트 마스크 나 비트 맵, N 부울 값 등으로. 이 사용은 종종 변수의 정확한 크기를 알고 싶어하기 때문에 uint32_t
및 같은 고정 너비 유형과 함께 사용됩니다 uint64_t
. 특정 변수가이 치료를받을 권리가 있다는 힌트는 단지와 함께에서 작동한다는 것입니다 비트 와 같은 통신 사업자 ~
, |
, &
, ^
, >>
등, 그리고 같은 산술 연산과 +
, -
, *
, /
등
비트 연산자의 동작이 잘 정의되고 표준화되어 있기 때문에 Unsigned가 이상적입니다. 부호있는 값에는 이동시 정의되지 않은 동작과 지정되지 않은 동작, 지정되지 않은 표현과 같은 몇 가지 문제가 있습니다.
실제로 모듈 식 산술을 원할 때. 때로는 실제로 2 ^ N 모듈 식 산술을 원합니다. 이 경우 "오버플로"는 버그가 아니라 기능입니다. 부호없는 값은 모듈 식 산술을 사용하도록 정의되었으므로 여기에서 원하는 것을 제공합니다. 부호있는 값은 지정되지 않은 표현이 있고 오버플로가 정의되지 않았기 때문에 (쉽고 효율적으로) 전혀 사용할 수 없습니다.
0.5 내가 이것을 쓴 후에 나는 이것이 내가 보지 못했던 Jarod의 예 와 거의 동일하다는 것을 깨달았습니다. 그리고 좋은 이유로 좋은 예입니다!
1 우리는 size_t
여기서 말하는 것이므로 일반적으로 32 비트 시스템에서는 2 ^ 32-1, 64 비트 시스템에서는 2 ^ 64-1입니다.
2 C ++에서는 부호없는 값이 해당 부호있는 유형보다 상단에 더 많은 값을 포함하기 때문에 정확히 그렇지는 않지만 부호없는 값을 조작하면 (논리적으로) 부호있는 값이 생성 될 수 있다는 기본적인 문제가 있지만 해당 문제는 없습니다. 부호있는 값 포함 (부호있는 값에는 이미 부호없는 값이 포함되어 있으므로)
unsigned int x = 0; --x;
무엇이 될지 보십시오x
. 제한 검사가 없으면 크기가 갑자기 UB로 쉽게 이어질 수있는 예상치 못한 값을 얻을 수 있습니다.