signed int 대신 unsigned를 사용하면 버그가 발생할 가능성이 더 큽니까? 왜?


82

에서 구글 C ++ 스타일 가이드 "부호없는 정수"의 주제에, 그것은하는 것이 좋습니다

역사적 사고로 인해 C ++ 표준은 컨테이너의 크기를 나타 내기 위해 부호없는 정수를 사용합니다. 표준기구의 많은 구성원은 이것이 실수라고 생각하지만이 시점에서 수정하는 것은 사실상 불가능합니다. 부호없는 산술은 단순한 정수의 동작을 모델링하지 않고 대신 모듈 식 산술 (오버플로 / 언더 플로에서 래핑)을 모델링하기 위해 표준에 의해 정의된다는 사실은 컴파일러에서 중요한 버그 클래스를 진단 할 수 없음을 의미합니다.

모듈 식 산술의 문제점은 무엇입니까? unsigned int의 예상되는 동작이 아닙니까?

이 가이드에서는 어떤 종류의 버그 (중요한 클래스)를 언급합니까? 범람하는 버그?

부호없는 유형을 사용하여 변수가 음수가 아니라고 단언하지 마십시오.

내가 unsigned int보다 signed int를 사용하는 것을 생각할 수있는 한 가지 이유는 그것이 오버플로 (음수로)되면 감지하기 더 쉽기 때문입니다.


4
시도하고 unsigned int x = 0; --x;무엇이 될지 보십시오 x. 제한 검사가 없으면 크기가 갑자기 UB로 쉽게 이어질 수있는 예상치 못한 값을 얻을 수 있습니다.
일부 프로그래머 친구

33
적어도 서명되지 않은 오버플로는 잘 정의 된 동작을 가지며 예상되는 결과를 생성합니다.
user7860670

35
관련이없는 (귀하의 질문과는 다르지만 Google 스타일 가이드와는 아님) 메모에서 조금만 검색하면 Google 스타일 가이드에 대한 일부 (때로는 정당하게) 비판을 찾을 수 있습니다. 그들을 복음으로 받아들이지 마십시오.
일부 프로그래머 친구

18
반면 int오버플로와 언더 플로는 UB입니다. 0 이하로 int감소하는 상황보다 가치를 표현하려고 하는 상황을 경험할 가능성이 적지 unsigned intunsigned int산술 의 행동에 놀라 는 사람은 또한 할 수있는 사람입니다. int오버플로 a < a + 1를 확인 하는 데 사용 하는 것과 같이 오버플로 관련 UB를 유발하는 코드를 작성 하십시오.
François Andrieux

12
부호없는 정수가 오버플로되면 잘 정의 된 것입니다. 부호있는 정수가 오버플로되면 정의되지 않은 동작입니다. 잘 정의 된 동작을 선호하지만 코드가 오버플로 된 값을 처리 할 수 ​​없으면 둘 다 손실됩니다. 차이점은 서명 된 경우 다음 코드에서 서명되지 않은 경우 오버플로 작업으로 인해 이미 손실되었습니다. 내가 동의하는 유일한 요점은 음수 값이 필요한 경우 부호없는 정수 유형이 잘못된 선택이라는 것입니다.
이 사이트에 대한 너무 정직

답변:


71

답변 중 일부는 여기에 서명 부호없는 값 사이의 놀라운 프로모션 규칙을 언급하지만 더 많은 관련 문제처럼 보인다 혼합 서명과 서명되지 않은 값을, 왜 반드시 설명하지 않습니다 서명 변수가 선호 될 것이다 서명되지 않은 시나리오를 혼합의 외부.

내 경험상 혼합 비교와 승격 규칙 외에 부호없는 값이 다음과 같이 버그 자석 인 두 가지 주요 이유가 있습니다.

부호없는 값은 프로그래밍에서 가장 일반적인 값인 0에서 불연속성을 갖습니다.

부호없는 정수와 부호있는 정수는 모두 최소값과 최대 값에서 불연속성 을 가지며, 여기에서 줄 바꿈 (부호 없음)하거나 정의되지 않은 동작 (부호 있음)을 유발합니다. 들어 unsigned이러한 점에있다 제로UINT_MAX. 들어 int가에있다 INT_MIN하고 INT_MAX. 전형적인 값 INT_MININT_MAX4 바이트로 시스템 int값은 다음 -2^312^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++) { // do something }

이것은 언젠가 빈 벡터를 전달할 때까지 잘 작동합니다. 0 반복을 수행하는 대신 v.size() - 1 == a giant number1 을 얻고 40 억 반복을 수행하고 거의 버퍼 오버플로 취약성을 갖습니다.

다음과 같이 작성해야합니다.

for (size_t i = 0; i + 1 < v.size(); i++) { // do something }

따라서이 경우 "고정"될 수 있지만의 서명되지 않은 특성에 대해 신중하게 생각해야만합니다 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 ++에서는 부호없는 값이 해당 부호있는 유형보다 상단에 더 많은 값을 포함하기 때문에 정확히 그렇지는 않지만 부호없는 값을 조작하면 (논리적으로) 부호있는 값이 생성 될 수 있다는 기본적인 문제가 있지만 해당 문제는 없습니다. 부호있는 값 포함 (부호있는 값에는 이미 부호없는 값이 포함되어 있으므로)


10
나는 당신이 게시 한 모든 것에 동의한다. 그러나 "64 비트는 모두에게 충분해야한다"는 것은 확실히 "640k는 모두에게 충분해야한다"에 너무 가깝게 보인다.
Andrew Henle

6
@Andrew-그래, 내 말을 신중하게 선택했습니다 :).
BeeOnRope

4
"64 비트는 부호없는 값에 대한 문을 닫습니다."-> 동의하지 않습니다. 일부 정수 프로그래밍 작업은 계산의 경우가 아니라 단순하며 음수 값이 필요하지 않지만 2의 제곱 너비가 필요합니다. 암호, 암호화, 비트 그래픽, 부호없는 수학의 이점. 여기서 많은 아이디어가 왜 코드가 서명 된 수학을 사용할 수 있는지를 지적하지만 서명되지 않은 유형을 쓸모 없게 만들고 문을 닫는 데는 매우 부족 합니다.
chux-Monica 복원

2
@Deduplicator-네, 넥타이처럼 보이므로 생략했습니다. 서명되지 않은 mod-2 ^ N 랩 어라운드 측면에서는 최소한 정의 된 동작이 있고 예기치 않은 "최적화"가 시작되지 않습니다. UB 측면에서는 서명되지 않거나 서명 된 연산 중 오버플로가 압도적 다수의 버그 일 수 있습니다. (mod 산술을 기대하는 소수의 경우를 제외하고) 컴파일러는 -ftrapv모든 서명 된 오버플로를 포착 할 수 있지만 모든 서명되지 않은 오버플로를 포착 할 수 있는 옵션을 제공 합니다. 성능에 미치는 영향은 그리 나쁘지 않으므로 -ftrapv일부 시나리오에서는 컴파일하는 것이 합리적 일 수 있습니다 .
BeeOnRope

2
@BeeOnRope That's about the age of the universe measured in nanoseconds.나는 그것을 의심합니다. 우주에 관한 13.7*10^9 years되는 기존 4.32*10^17 s또는4.32*10^26 ns . 4.32*10^26int로 표현하려면 최소한 90 bits. 9,223,372,036,854,775,807 ns에 대한 것 292.5 years입니다.
Osiris

37

언급했듯이 혼합 unsignedsigned (잘 정의 된 경우에도) 예상치 못한 동작이 발생할 수 있습니다.

마지막 5 개를 제외한 벡터의 모든 요소를 ​​반복한다고 가정하면 다음과 같이 잘못 작성할 수 있습니다.

for (int i = 0; i < v.size() - 5; ++i) { foo(v[i]); } // Incorrect
// for (int i = 0; i + 5 < v.size(); ++i) { foo(v[i]); } // Correct

가정 v.size() < 5으로, 다음, v.size()이다 unsigned, s.size() - 5매우 많은 수의 것, 그래서 i < v.size() - 5true의 가치를 더 예상 범위 i. 그리고 UB는 빠르게 발생합니다 (한 번i >= v.size() )

v.size()부호있는 값을 반환 하면s.size() - 5 음수가되고 위의 경우 조건은 즉시 거짓이됩니다.

반면에 인덱스는 사이에 [0; v.size()[있어야 unsigned하므로 의미가 있습니다. Signed는 또한 음수 부호의 오른쪽 시프트에 대한 오버플로 또는 구현 정의 동작이있는 UB로 자체 문제가 있지만 반복에 대한 버그의 원인은 적습니다.


2
내가 할 수있을 때마다 서명 된 숫자를 사용하지만이 예제가 충분히 강력하다고 생각하지 않습니다. 확실히, 오랜 시간 동안 서명되지 않은 번호를 사용이 관용구를 아는 사람 : 대신 i<size()-X, 하나 작성해야합니다 i+X<size(). 물론 기억해야 할 일이지만 제 생각에는 익숙해지는 것이 그렇게 어렵지 않습니다.
geza

8
당신이 말하는 것은 기본적으로 언어와 유형 간의 강제 규칙을 알아야한다는 것입니다. 나는 이것이 질문에서 요구하는대로 서명되거나 서명되지 않은 것을 사용하는지 여부를 어떻게 변경하는지 보지 못했습니다. 음수 값이 필요하지 않은 경우 서명을 사용하지 않는 것이 좋습니다. @geza에 동의하며 필요한 경우 에만 서명을 사용 합니다. 이것은 Google 가이드 를 기껏해야 의심스럽게 만듭니다 . 이모 나쁜 조언입니다.
이 사이트에 대한 너무 정직

2
@toohonestforthissite 요점은 규칙이 비밀스럽고 조용하며 버그의 주요 원인이라는 것입니다. 산술에 독점적으로 서명 된 유형을 사용하면 문제를 해결할 수 있습니다. 양수 값을 적용하기 위해 서명되지 않은 유형을 사용하는 BTW는 최악의 남용 중 하나입니다.
Passer By

2
고맙게도 최신 컴파일러와 IDE는 식에서 부호있는 숫자와 부호없는 숫자를 혼합 할 때 경고를 표시합니다.
Alexey B.

5
@PasserBy : 당신이 그들을 신비라고 부르면, 당신은 또한 정수 승격과 부호있는 타입의 오버 플로우를위한 UB를 추가해야합니다. 당신은 너무 그리고 매우 일반적인를 sizeof 연산자는 부호없는 어쨌든을 반환 해야합니까 그들에 대해 알고있다. 언어 세부 사항을 배우고 싶지 않다면 C 나 C ++를 사용하지 마세요! Google 프로모션을 고려할 때 아마도 그것이 그들의 목표 일 것입니다. "악을하지 말라"의 일이 긴 ... 사라 졌어요
너무 정직이 사이트

20

가장 큰 오류의 예 중 하나는 부호있는 값과 부호없는 값을 혼합하는 경우입니다.

#include <iostream>
int main()  {
    auto qualifier = -1 < 1u ? "makes" : "does not make";
    std::cout << "The world " << qualifier << " sense" << std::endl;
}

출력 :

세상이 말이 안돼

사소한 응용 프로그램이없는 한, 서명 된 값과 서명되지 않은 값 사이에 위험한 혼합 (런타임 오류 발생)이 발생하거나 경고를 크랭크업하여 컴파일 타임 오류로 만들면 결국 많은 코드의 static_casts. 그렇기 때문에 수학 또는 논리적 비교를 위해 유형에 부호있는 정수를 엄격하게 사용하는 것이 가장 좋습니다. 비트 마스크 및 비트를 나타내는 유형에만 unsigned를 사용하십시오.

숫자 값의 예상 도메인을 기반으로 부호없는 유형을 모델링하는 것은 나쁜 생각입니다. 대부분의 숫자는 20 억보다 0에 가까우므로 부호없는 유형의 경우 많은 값이 유효한 범위의 가장자리에 더 가깝습니다. 설상가상으로 최종 값은 알려진 양의 범위에있을 수 있지만 표현식을 평가하는 동안 중간 값이 언더 플로 될 수 있으며 중간 형식으로 사용되는 경우 매우 잘못된 값이 될 수 있습니다. 마지막으로, 값이 항상 양수일 것으로 예상 되더라도 음수 있는 다른 변수 와 상호 작용하지 않는다는 의미는 아니므로 부호있는 유형과 부호없는 유형을 혼합하는 강제 상황이 발생합니다. 최악의 장소.


8
숫자 값의 예상 도메인을 기반으로 서명되지 않은 유형을 모델링하는 것은 잘못된 생각입니다. * 암시 적 변환을 경고로 취급하지 않고 적절한 유형 캐스트를 사용하기에는 너무 게으른 경우 * 예상되는 유효한 유형에 따라 유형 모델링 기본 제공 유형이있는 C / C ++에서는 값이 완전히 합리적입니다.
villasv

1
@ user7586189 유효하지 않은 데이터를 인스턴스화하는 것을 불가능하게 만드는 것이 좋은 습관이므로 크기에 대해 양수 전용 변수를 갖는 것이 완벽하게 합리적입니다. 그러나이 답변과 같은 잘못된 캐스트를 기본적으로 허용하지 않도록 C / C ++ 기본 제공 유형을 미세 조정할 수 없으며 유효성은 다른 사람의 책임으로 끝납니다. 더 엄격한 캐스트를 사용하는 언어를 사용하는 경우 (내장 된 경우에도) 예상 도메인 모델링은 꽤 좋은 생각입니다.
villasv

1
참고, 나는 않았다 경고를 본격 오류로 설정 언급하지만, 모두가 않습니다. 나는 여전히 @villasv가 모델링 가치에 대한 당신의 진술에 동의하지 않습니다. unsigned를 선택하면 그것이 무엇인지에 대한 많은 예지없이 접촉 할 수있는 다른 모든 값을 암시 적으로 모델링 하는 것입니다. 그리고 거의 확실하게 잘못되었습니다.
Chris Uzdavinis

1
도메인을 염두에두고 모델링하는 것은 좋은 일입니다. unsigned를 사용하여 도메인을 모델링하는 것은 아닙니다. ( 다른 방법으로 수행 할 수없는 경우를 제외하고 는 범위가 아니라 사용 유형을 기준으로 서명 됨과 서명되지 않음을 선택해야합니다 .)
Chris

2
코드베이스에 부호있는 값과 부호없는 값이 혼합되어 있으면 경고를 표시하고이를 오류로 승격하면 코드가 static_cast로 흩어져 변환을 명시 적으로 만듭니다 (수학은 여전히 ​​수행되어야하기 때문입니다.). 정확하더라도, 오류가 발생하기 쉽고 작업하기가 더 어렵고 읽기가 더 어렵습니다.
Chris Uzdavinis

11

unsigned int를 사용하는 것이 signed int를 사용하는 것보다 버그를 일으킬 가능성이 더 높은 이유는 무엇입니까?

서명되지 않은 유형을 사용하는 것은 특정 작업 클래스에 서명 된 유형을 사용하는 것보다 버그를 일으킬 가능성이 더 큽니다 .

작업에 적합한 도구를 사용하십시오.

모듈 식 산술의 문제점은 무엇입니까? unsigned int의 예상되는 동작이 아닙니까?
unsigned int를 사용하는 것이 signed int를 사용하는 것보다 버그를 일으킬 가능성이 더 높은 이유는 무엇입니까?

작업이 잘 일치하는 경우 : 잘못된 것은 없습니다. 아니, 그럴 것 같지는 않다.

보안, 암호화 및 인증 알고리즘은 서명되지 않은 모듈 식 수학을 사용합니다.

압축 / 압축 해제 알고리즘과 다양한 그래픽 형식도 이점이 있으며 서명되지 않은 경우 버그가 적습니다. 수학에서 .

비트 연산자와 시프트가 사용될 때마다 부호없는 연산은 부호있는 수학 의 부호 확장 문제로 엉망이되지 않습니다 .


부호있는 정수 수학은 코딩에 대한 학습자를 포함한 모든 사람이 쉽게 이해할 수있는 직관적 인 모양과 느낌을 가지고 있습니다. C / C ++는 원래 대상이 아니 었으며 지금은 소개 언어가되어서는 안됩니다. 오버플로와 관련된 안전망을 사용하는 신속한 코딩의 경우 다른 언어가 더 적합합니다. 간결하고 빠른 코드의 경우 C는 코더가 자신이하는 일을 알고 있다고 가정합니다 (경험이 있음).

오늘날 부호있는 수학 의 함정은 유비쿼터스 32 비트로 int, 너무 많은 문제가있어 범위 검사없이 일반적인 작업을 수행하기에 충분히 넓습니다. 이것은 오버플로가 코딩되지 않는 안일함으로 이어집니다. 대신 for (int i=0; i < n; i++) int len = strlen(s);때문에 OK로 볼 n가정 < INT_MAX문자열이 너무 오래,보다는 전체 첫 번째 경우에 보호 또는 사용하여 원거리 될 수 없으며 size_t, unsigned심지어 long long2 년.

16 비트 int와 32 비트가 포함 된 시대에 개발 된 C / C ++ 와 서명되지 않은 16 비트가 size_t제공 하는 추가 비트 는 중요했습니다. 주의는 문제가 될 오버 플로우 관련하여 필요했다 intunsigned.

16 비트가 아닌 int/unsigned플랫폼에서 Google의 32 비트 (또는 더 넓은) 애플리케이션을 사용 int하면 충분한 범위 를 고려할 때 +/- 오버플로 에 대한 주의가 부족 합니다. 이러한 응용 프로그램을 장려하기 위해이 말이 intunsigned. 그러나 int수학은 잘 보호되지 않습니다.

좁은 16 비트 int/unsigned문제는 오늘날 일부 임베디드 애플리케이션에 적용됩니다.

Google의 지침은 오늘날 작성하는 코드에 잘 적용됩니다. 더 넓은 범위의 C / C ++ 코드에 대한 명확한 지침이 아닙니다.


내가 unsigned int보다 signed int를 사용하는 것을 생각할 수있는 한 가지 이유는 그것이 오버플로 (음수로)되면 감지하기 더 쉽기 때문입니다.

C / C ++에서 부호있는 int 수학 오버플로는 정의되지 않은 동작 이므로 서명되지 않은 수학의 정의 된 동작보다 감지하기가 확실히 쉽지 않습니다 .


따라 @ 크리스 Uzdavinis가 아니라 주석, 혼합 서명부호는 최고의 모든 (특히 초보자)에 의해 피할 필요로 할 때, 그렇지 않으면주의 깊게 코딩된다.


2
당신 int은 또한 "실제"정수의 동작을 모델링하지 않는다는 점을 지적 합니다. 오버플로에 대한 정의되지 않은 동작은 수학자가 정수를 생각하는 방식이 아닙니다. 추상 정수로 "오버플로"할 가능성이 없습니다. 하지만 이것들은 기계 저장 장치이지 수학 전문가의 숫자가 아닙니다.
tchrist

1
@tchrist : 오버플로에 대한 부호없는 동작은 수학자가 정수 합동 모드 (type_MAX + 1)의 추상 대수 링에 대해 생각하는 방법입니다.
supercat

gcc를 사용하는 경우 signed int오버플로는 쉽게 감지 할 수 있지만 ( 사용시 -ftrapv) 서명되지 않은 "오버플로"는 감지하기 어렵습니다.
anatolyg

5

저는 Google의 스타일 가이드 인 AKA the Hitchhiker 's Guide to Insane Directives from the Bad Programmers Who Got into the Long Time Ago에 대한 경험이 있습니다. 이 특정 지침은 그 책에있는 수십 가지의 열매 규칙 중 하나 일뿐입니다.

부호없는 유형으로 산술을 수행하려는 경우에만 오류가 발생합니다 (위의 Chris Uzdavinis 예제 참조), 즉 숫자로 사용하는 경우 오류가 발생합니다. 서명되지 않은 유형은 숫자 수량을 저장하는 데 사용되는 것이 아니라 음수가 될 수없는 컨테이너 크기와 같은 개수 를 저장하기위한 것이며 해당 목적으로 사용할 수 있고 사용해야합니다.

컨테이너 크기를 저장하기 위해 산술 유형 (예 : 부호있는 정수)을 사용하는 아이디어는 바보입니다. 목록의 크기를 저장하기 위해 double을 사용 하시겠습니까? Google에 산술 유형을 사용하여 컨테이너 크기를 저장하고 다른 사람들에게 동일한 작업을 요구하는 사람들이 있다는 것은 회사에 대해 뭔가를 말해줍니다. 그러한 지시에 대해 제가 알아 차린 한 가지는 그들이 바보 일수록 더 엄격한 규칙을해야한다는 것입니다. 그렇지 않으면 상식을 가진 사람들이 규칙을 무시할 것이기 때문입니다.


드리프트를 얻는 동안, unsigned형식이 개수 만 보유하고 산술에 사용할 수없는 경우 작성된 ​​총괄 명령문은 비트 연산을 사실상 제거 할 것 입니다. 따라서 "나쁜 프로그래머의 미친 지시" 부분이 더 의미가 있습니다.
David C. Rankin

@ DavidC.Rankin "담요"진술로 받아들이지 마십시오. 분명히 부호없는 정수 (비트 값 저장과 같은)에 대한 여러 합법적 인 용도가 있습니다.
Tyler Durden

네, 네. 안 했어요. 그래서 "나는 당신의 표류를 얻습니다."라고 말했습니다.
David C. Rankin

1
개수는 종종 인덱스와 같이 산술이 수행 된 항목과 비교됩니다. C가 부호있는 숫자와 부호없는 숫자를 포함하는 비교를 처리하는 방식은 많은 이상한 단점을 유발할 수 있습니다. 카운트의 최상위 값이 부호없는 유형에 맞지만 해당 부호있는 유형에는 맞지 않는 경우를 제외하고 ( int16 비트 인 날에는 일반적 이지만 오늘날에는 훨씬 적습니다) 숫자처럼 작동하는 카운트를 갖는 것이 좋습니다.
supercat

1
"오류는 부호없는 유형으로 산술을 수행하려고 할 때만 발생합니다."-항상 발생합니다. "컨테이너 크기를 저장하기 위해 산술 유형 (예 : 부호있는 정수)을 사용하는 아이디어는 어리 석습니다."-그렇지 않으며 C ++위원회는 이제 size_t를 사용하는 것이 역사적 실수로 간주됩니다. 이유? 암시 적 변환.
Átila Neves

1

부호없는 형식을 사용하여 음이 아닌 값을 나타냅니다 ...

  • 다른 답변이 자세히 설명하고 논의 하듯이 부호있는 값과 부호없는 값을 사용할 때 유형 승격과 관련된 버그를 일으킬 가능성더 높지만
  • 바람직 하지 않거나 허용되지 않는 값을 나타낼 수있는 도메인을 가진 유형 선택과 관련된 버그를 일으킬 가능성적습니다 . 어떤 곳에서는 가치가 도메인에 있다고 가정하고 다른 가치가 어떻게 든 몰래 들어 오면 예상치 못한 잠재적으로 위험한 행동이 발생할 수 있습니다.

Google 코딩 가이드 라인은 첫 번째 고려 사항에 중점을 둡니다. C ++ Core Guidelines 와 같은 다른 지침 세트 는 두 번째 요점을 더 강조합니다. 예를 들어 핵심 가이드 라인 I.12를 고려하십시오 .

I.12 : null이 아니어야하는 포인터를 다음과 같이 선언합니다. not_null

이유

nullptr 오류를 역 참조하지 않도록합니다. 에 대한 중복 검사를 방지하여 성능을 향상시킵니다 nullptr.

int length(const char* p);            // it is not clear whether length(nullptr) is valid
length(nullptr);                      // OK?
int length(not_null<const char*> p);  // better: we can assume that p cannot be nullptr
int length(const char* p);            // we must assume that p can be nullptr

소스에 의도를 명시함으로써 구현 자와 도구는 정적 분석을 통해 일부 오류 클래스를 찾는 것과 같은 더 나은 진단을 제공하고 분기 제거 및 널 테스트와 같은 최적화를 수행 할 수 있습니다.

물론, non_negative두 범주의 오류를 모두 방지 하는 정수용 래퍼에 대해 주장 할 수 있지만 자체 문제가 있습니다.


0

Google 문은 컨테이너의 크기 유형으로 서명되지 않음을 사용 하는 것에 관한 것입니다. 입니다. 대조적으로 질문은 더 일반적으로 보입니다. 계속 읽는 동안 명심하십시오.

지금까지 대부분의 답변이 Google 진술에 반응했기 때문에 더 큰 질문에는 덜 반응했기 때문에 음수 컨테이너 크기에 대한 답변을 시작하고 나중에 서명되지 않은 것이 좋다고 누구에게나 설득하려고 노력할 것입니다.

서명 된 컨테이너 크기

누군가가 버그를 코딩하여 부정적인 컨테이너 인덱스를 생성했다고 가정 해 보겠습니다. 결과는 정의되지 않은 동작 또는 예외 / 액세스 위반입니다. 인덱스 유형이 서명되지 않았을 때 정의되지 않은 동작이나 예외 / 액세스 위반을 얻는 것보다 정말 낫습니까? 아니에요.

이제 수학에 대해 이야기하고이 맥락에서 "자연스러운"것이 무엇인지 말하는 것을 좋아하는 부류의 사람들이 있습니다. 음수를 갖는 정수 유형이 본질적으로> = 0 인 무언가를 설명하는 데 어떻게 자연 스러울 수 있습니까? 음수 크기의 배열을 많이 사용하십니까? IMHO, 특히 수학적 경향이있는 사람들은 이러한 의미의 불일치 (크기 / 인덱스 유형은 음수가 가능하다고 말하는 반면 음의 크기 배열은 상상하기 어렵습니다)를 짜증나게 할 것입니다.

따라서이 문제에 대한 유일한 질문은 Google 주석에서 언급했듯이 컴파일러가 실제로 그러한 버그를 찾는 데 적극적으로 도움을 줄 수 있는지 여부입니다. 그리고 서명되지 않은 정수를 언더 플로 보호하는 대안보다 훨씬 좋습니다 (x86-64 어셈블리 및 다른 아키텍처에는이를 달성 할 수단이있을 수 있지만 C / C ++ 만 이러한 수단을 사용하지 않습니다). 내가 추측 할 수있는 유일한 방법은 컴파일러가 런타임 검사 ( if (index < 0) throwOrWhatever)를 자동으로 추가 하거나 컴파일 시간 작업의 경우 잠재적으로 많은 오 탐지 경고 / 오류를 생성하는 경우입니다. "이 배열 액세스에 대한 인덱스는 음수가 될 수 있습니다." 의심 스럽지만 도움이 될 것입니다.

또한 실제로 런타임 검사를 작성하여 배열 / 컨테이너 인덱스를 작성하는 사람들은 부호있는 정수를 다루는 작업 이 더 많습니다 . 작성하는 대신 if (index < container.size()) { ... }이제 작성 해야 if (index >= 0 && index < container.size()) { ... }합니다.. 나에게 강제 노동처럼 보이며 개선이 아닌 것 같습니다 ...

서명되지 않은 유형이없는 언어는 짜증납니다 ...

예, 이것은 자바에서 찌르는 것입니다. 이제 저는 임베디드 프로그래밍 배경에서 왔으며 바이너리 연산 (및 / 또는 xor, ...)과 비트 현명한 값 구성이 말 그대로 빵과 버터 인 필드 버스로 많은 작업을했습니다. 우리 제품 중 하나에 대해 우리 또는 오히려 고객은 자바 포트를 원했습니다. 그는 침착 함을 유지하려고 노력했고 침묵 속에서 고통 받았습니다 ... 그러나 고통은 거기에 있었고, 서명되지 않은 정수 값을 지속적으로 처리 한 며칠 후에 저주를 멈출 수 없었습니다. 이러한 시나리오는 고통스럽고 개인적으로 Java가 부호있는 정수를 생략하고 부호없이 제공했다면 Java가 더 나았을 것이라고 생각합니다. 적어도 부호 확장 등에 대해서는 신경 쓸 필요가 없습니다.

이것들은 문제에 대한 나의 5 센트입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.