programing

서명되지 않고 서명되지 않은 int를 사용하는 것이 버그를 일으킬 가능성이 높은가요?왜요?

goodcopy 2022. 7. 28. 22:43
반응형

서명되지 않고 서명되지 않은 int를 사용하는 것이 버그를 일으킬 가능성이 높은가요?왜요?

Google C++ 스타일 가이드의 "부호되지 않은 정수" 항목에서 다음을 권장합니다.

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

모듈식 산술의 문제점은 무엇입니까?그게 서명되지 않은 int의 예상된 행동 아닌가요?

가이드에서는 어떤 종류의 버그(중요한 클래스)를 언급하고 있습니까?넘쳐나는 벌레?

변수가 음수가 아니라고 주장하기 위해 부호 없는 유형을 사용하지 마십시오.

서명되지 않은 int보다 서명된 int를 사용하는 것을 생각할 수 있는 이유 중 하나는 (네거티브로) 오버플로가 발생하면 검출이 쉬워지기 때문입니다.

여기에서는 서명된 값과 서명되지 않은 값 사이의 놀라운 프로모션 규칙을 언급하고 있지만, 이는 서명되지 않은 값과 서명되지 않은 값을 혼합하는 과 관련된 문제로 보이며, 혼합 시나리오 이외의 경우 서명되지 않은 변수가 선호되는 이유를 반드시 설명하지는 않습니다.

제 경험상, 비교와 프로모션이 혼재된 규칙 외에 부호 없는 값이 버그 자석인 주된 이유는 두 가지입니다.

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

부호 없는 정수 및 부호 있는 정수 모두 최소값과 최대값으로 불연속성이 있으며, 여기서 둘레(서명되지 않음) 또는 정의되지 않은 동작(서명됨)이 발생합니다.위해서unsigned이 점들은 0이고UINT_MAX.위해서int그들은 에 있다INT_MIN그리고.INT_MAX. 의 일반적인 값INT_MIN그리고.INT_MAX4바이트의 시스템으로int값은 다음과 같습니다.-2^31그리고.2^31-1, 및 이러한 시스템 상에 있습니다.UINT_MAX일반적으로는2^32-1.

버그를 유발하는 주요 문제unsigned에 해당되지 않는다int0에 불연속부가 있다는 거죠물론 0은 1, 2, 3과 같은 다른 작은 값과 함께 프로그램에서 매우 일반적인 값입니다.다양한 구조에서 작은 값, 특히 1을 더하고 빼는 것이 일반적이며, 만약 당신이 어떤 것을 뺄 때unsigned값이 0이 되면 엄청난 양의 값과 거의 확실한 버그를 얻을 수 있습니다.

마지막을0.5 제외한 벡터 내의 모든 값에 대해 인덱스로 코드가 반복된다고 가정합니다.

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

이것은 어느 날 빈 벡터를 통과할 때까지 정상적으로 동작합니다.반복을 전혀 하지 않는 대신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일 때 경고할 수 있지만 실행 시 결정된 값일 경우에는 경고하지 않습니다.

대척점

어떤 사람들은 서명된 값도 두 개의 불연속성을 가지고 있다고 주장할 수 있는데, 왜 서명되지 않은 값을 골랐을까요?차이점은 두 불연속 모두 0에서 매우(최대) 멀리 떨어져 있다는 것입니다.저는 이것이 "오버플로우"의 별개의 문제라고 생각합니다.서명된 값과 서명되지 않은 값은 모두 매우 큰 값으로 오버플로우될 수 있습니다.대부분의 경우 가능한 값의 범위에 대한 제약으로 인해 오버플로우가 불가능하며 64비트 값의 오버플로우는 물리적으로 불가능할 수 있습니다).가능한 경우라도 오버플로 관련 버그가 발생할 가능성은 제로인 버그에 비해 거의 없으며, 서명되지 않은 값에서도 오버플로가 발생합니다.따라서 부호 없는 것은 두 세계 중 최악을 결합합니다. 즉, 잠재적으로 매우 큰 크기의 값으로 오버플로우하고 0에서 중단됩니다.서명에는 전자만 있습니다.

많은 사람들이 서명하지 않은 채 "당신이 좀 졌어요"라고 주장할 것입니다.이는 종종 사실이지만 항상 그렇지는 않습니다(부호가 없는 값의 차이를 나타내야 하는 경우에는 32비트의 많은 것이 2GiB로 제한되어 있거나 파일이 4GiB라고 하는 희한한 회색 영역이 존재하지만 두 번째 2GiB의 절반에서는 특정 API를 사용할 수 없습니다).

서명하지 않고 조금 사는 경우라도, 20억개 이상의 「물건」을 서포트해야 한다면, 곧 40억개 이상을 서포트해야 할 것입니다.

논리적으로 부호 없는 값은 부호 있는 값의 하위 집합입니다.

수학적으로 부호 없는 값(음수가 아닌 정수)은 부호 있는 정수(_정수라고만 함)2의 하위 집합입니다.그러나 서명된 값은 뺄셈과 같은 서명되지 않은 값에만 따라 자연스럽게 연산에서 튀어나옵니다.부호 없는 값은 뺄셈으로 닫히지 않는다고 할 수 있습니다.서명된 값에는 해당되지 않습니다.

서명되지 않은 두 인덱스 사이의 "delta"를 파일로 찾으시겠습니까?올바른 순서로 뺄셈을 하는 것이 좋다.그렇지 않으면 틀린 답을 얻을 수 있다.물론 올바른 순서를 결정하기 위해 런타임 체크가 필요한 경우가 많습니다.부호 없는 값을 숫자로 취급할 때는 (논리적으로) 부호 있는 값이 계속 표시되므로 먼저 부호 있는 값으로 시작하는 것이 좋습니다.

대척점

위의 각주 (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비트 값에 대해 발생합니다.따라서 부호 없는 값의 양의 범위를 2배로 늘림으로써 충분히 납득할 수 있는 경우 오버플로는 문제가 되고 부호 없는 값을 약간 선호한다는 사실을 입증할 수 있습니다.

전문 도메인 이외의 64비트 값은 이 문제를 대부분 해소합니다.부호 있는 64비트 값의 상한 범위는 9,223,372,036,854,775,807로 9조보다 큽니다.그것은 많은 나노초와 많은 돈이다.또, 어느 컴퓨터보다 큰 어레이로, 오랫동안 일관된 주소 공간에 RAM을 탑재하고 있을 가능성이 있습니다.그럼 9조 정도면 모두에게 충분한가?

부호 없는 값을 사용하는 경우

스타일 가이드는 부호 없는 숫자의 사용을 금지하거나 권장하지 않습니다.결론은 다음과 같습니다.

변수가 음수가 아니라고 주장하기 위해 부호 없는 유형을 사용하지 마십시오.

실제로 부호 없는 변수에는 다음과 같은 유용한 용도가 있습니다.

  • N비트 양을 정수가 아닌 단순히 "비트 가방"으로 취급하는 경우.예를 들어 비트마스크 또는 비트맵, N 부울 값 등입니다.이 용도는 다음과 같은 고정 폭 타입과 밀접하게 관련되어 있습니다.uint32_t그리고.uint64_t변수의 정확한 크기를 알고 싶은 경우가 많기 때문입니다.특정 변수는 다음과 같은 비트 연산자를 통해서만 연산할 수 있습니다.~,|,&,^,>>산술 연산으로는 할 수 없습니다.+,-,*,/기타.

    여기서 부호 없음은 비트 연산자의 동작이 잘 정의되고 표준화되기 때문에 이상적입니다.서명된 값에는 이동 시 정의되지 않은 동작 및 지정되지 않은 표현 등 몇 가지 문제가 있습니다.

  • 실제로 모듈식 산수를 원할 때.때로는 실제로 2^N 모듈식 산술이 필요할 수 있습니다.이러한 경우, 「오버플로우」는 기능이지, 버그가 아닙니다.부호 없는 값은 모듈식 연산을 사용하도록 정의되어 있으므로 원하는 값을 제공합니다.서명된 값은 지정되지 않은 표현이 있고 오버플로가 정의되지 않았기 때문에 전혀 사용할 수 없습니다(간단하고 효율적으로).


0.5 이 글을 쓴 후 나는 이것이 Jarod의 예시와 거의 같다는 것을 깨달았다.이것은 내가 본 적이 없는 것이다.그리고 정당한 이유로, 그것은 좋은 예다!

1 우리 얘기 하는 거야size_t따라서 일반적으로 32비트 시스템에서는 2^32-1, 64비트 시스템에서는 2^64-1입니다.

2 C++에서는 부호 없는 값이 대응하는 부호 있는 유형보다 상단의 값을 더 많이 포함하기 때문에 이는 정확하게 해당되지 않지만 부호 없는 값을 조작하면 (논리적으로) 부호 있는 값이 될 수 있다는 기본적인 문제가 있지만 부호 있는 값에는 이미 부호 없는 값이 포함되어 있기 때문에 이에 대응하는 문제는 없습니다.

전술한 바와 같이 믹싱unsigned그리고.signed(잘 정의된 경우에도) 예기치 않은 동작을 일으킬 수 있습니다.

마지막 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그럼, as.v.size()unsigned,s.size() - 5아주 많은 수가 될 것이기 때문에i < v.size() - 5되지요true보다 기대되는 가치 범위를 위해i그 후 UB는 신속하게 실행된다(한 번 액세스 범위를 벗어남).i >= v.size())

한다면v.size()그러면 서명된 값이 반환됩니다.s.size() - 5음성이었을 수도 있고, 위의 경우 즉시 상태가 거짓이 될 수도 있습니다.

반대편에서 지수는 다음 값 사이에 있어야 합니다.[0; v.size()[그렇게unsigned말이 되네.서명에는 UB라는 자체적인 문제도 있습니다.부정적인 서명 번호의 오른쪽 시프트를 위한 오버플로 또는 구현 정의 동작이지만 반복을 위한 버그의 발생 빈도는 낮습니다.

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

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

출력:

세상이 말이 안 된다

사소한 어플리케이션이 없는 한 서명된 값과 서명되지 않은 값 사이에 위험한 혼재(실행 시 오류가 발생)가 발생하거나 경고를 기동하여 컴파일 시 오류가 발생하면 코드 내에 많은 static_cast가 발생합니다.그렇기 때문에 수학이나 논리적인 비교를 위해 형식에는 엄밀하게 부호 있는 정수를 사용하는 것이 가장 좋습니다.비트를 나타내는 비트 마스크 및 유형에만 부호 없이 사용하십시오.

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

서명되지 않은 int를 사용하는 것이 서명된 int를 사용하는 것보다 버그를 일으킬 가능성이 높은 이유는 무엇입니까?

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

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

모듈식 산술의 문제점은 무엇입니까?그게 서명되지 않은 int의 예상된 행동 아닌가요?
서명되지 않은 int를 사용하는 것이 서명된 int를 사용하는 것보다 버그를 일으킬 가능성이 높은 이유는 무엇입니까?

작업이 잘 일치하는 경우: 아무 문제 없습니다.아니, 그 이상은 아니야.

보안, 암호화 및 인증 알고리즘은 부호 없는 모듈러 계산에 포함됩니다.

압축/압축 해제 알고리즘과 다양한 그래픽 형식이 유용하고 부호 없는 산술이 덜 버그가 됩니다.

비트 단위 연산자와 시프트를 사용할 때마다 부호 없는 연산은 부호화된 산술의 부호 확장 문제와 혼동되지 않습니다.


부호화된 정수 수학은 직관적인 모양과 느낌을 가지며, 학습자를 포함한 모든 사람이 코딩에 대해 쉽게 이해할 수 있습니다.C/C++는 원래 타깃이 아니었으며 현재는 도입 언어로 되어 있어야 합니다.오버플로에 관한 안전망을 사용하는 신속한 코딩에는 다른 언어가 더 적합하다.린 퍼스트 코드의 경우 C는 코더가 자신이 무엇을 하고 있는지 알고 있다고 가정합니다(코더는 경험이 있습니다).

오늘날 서명된 수학의 함정은 어디서나 볼 수 있는 32비트입니다.int많은 문제를 안고 있는 것은 범위 확인 없이 일반적인 작업에 충분할 정도로 광범위합니다.이로 인해 오버플로는 코드화되지 않는 안일한 상태가 됩니다.대신,for (int i=0; i < n; i++) int len = strlen(s);정상으로 표시됩니다.n상정되는 것은 <<INT_MAX첫 번째 경우 풀 레인지 보호 또는 사용보다는 스트링이 너무 길지 않습니다.size_t,unsigned또는 심지어long long2일날.

C/C++는 16비트 및 32비트를 포함하는 시대에 개발되었습니다.int추가 비트는 부호 없는 16비트입니다.size_t제공이 중요했습니다.오버플로우 문제에 대한 주의가 필요했습니다.int또는unsigned.

구글의 32비트(또는 더 넓은) 응용 프로그램을 16비트 이외에서 사용하는 경우int/unsigned플랫폼에서는 +/- 오버플로에 대한 주의가 필요합니다.int그 넓은 범위를 고려하면.이러한 어플리케이션으로 인해 다음과 같은 어플리케이션이int에 걸쳐서unsigned.아직int수학은 잘 보호되지 않는다.

좁은 16비트int/unsigned현재 일부 임베디드 어플리케이션에서 문제가 발생합니다.

구글의 가이드라인은 오늘날 그들이 쓰는 코드에 잘 적용된다.C/C++ 코드의 광범위한 범위에 대한 명확한 지침은 아닙니다.


서명되지 않은 int보다 서명된 int를 사용하는 것을 생각할 수 있는 이유 중 하나는 (네거티브로) 오버플로가 발생하면 검출이 쉬워지기 때문입니다.

C/C++에서는 부호화된 int 산술 오버플로는 정의되지 않은 동작이기 때문에 부호화되지 않은 산술의 정의된 동작보다 검출하기가 쉽지 않습니다.


@Chris Uzdavinis가 잘 언급했듯이, 모든 사람(특히 초보자)이 서명과 서명섞는 것을 피하는 것이 가장 좋으며, 그 외에는 필요할 때 신중하게 코딩해야 한다.

저는 구글의 스타일 가이드인 '히치하이커 가이드'에 대해 경험이 있습니다.오래 전에 입사한 나쁜 프로그래머들이 만든 '정신나간 지시 가이드'입니다.이 특정한 지침은 그 책에 있는 수십 가지의 미친 규칙들의 한 예에 불과하다.

에러는 부호 없는 타입으로 연산하려고 할 때만 발생합니다(위의 Chris Uzdavinis 예 참조). 즉, 수치로 사용할 경우 발생합니다.부호 없는 유형은 숫자 수량을 저장하기 위해 사용되는 것이 아니라, 절대 음수가 될 수 없는 용기의 크기와 같은 카운트를 저장하기 위한 것이며, 이러한 용도로 사용할 수 있고 사용해야 합니다.

컨테이너 크기를 저장하기 위해 산술적 유형(부호 있는 정수 등)을 사용하는 생각은 어리석은 것입니다.리스트의 사이즈도 더블로 저장해 주시겠습니까?구글에는 산술적인 타입을 사용하여 컨테이너 사이즈를 저장하고 있으며, 다른 사람에게도 같은 것을 요구한다는 것은 회사에 대한 어떤 것을 말해준다.그런 지시들에 대해 내가 알아차린 한 가지는 그들이 멍청할수록, 그들은 더 엄격해질 필요가 있다는 것이다. 그렇지 않으면 상식을 가진 사람들은 그 규칙을 무시하게 될 것이기 때문이다.

부호 없는 유형을 사용하여 음이 아닌 값을 나타내는 중...

  • 다른 답변이 설명하고 자세히 설명하듯이 서명된 값과 서명되지 않은 값을 사용할 경우 유형 프로모션을 수반하는 버그가 발생할 가능성이 높지만,
  • 는 바람직하지 않은 값 또는 허용되지 않는 값을 나타낼 수 있는 도메인을 가진 유형의 선택과 관련된 버그를 일으킬 가능성이 낮습니다.경우에 따라서는, 그 값이 도메인내에 있는 것으로 간주해, 다른 값이 몰래 침입했을 때에 예기치 않은 위험한 동작이 발생할 가능성이 있습니다.

The Google Coding Guidelines puts emphasis on the first kind of consideration. Other guideline sets, such as the C++ Core Guidelines, put more emphasis on the second point. For example, consider Core Guideline I.12:

I.12: Declare a pointer that must not be null as not_null

Reason

To help avoid dereferencing nullptr errors. To improve performance by avoiding redundant checks for nullptr.

Example

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

By stating the intent in source, implementers and tools can provide better diagnostics, such as finding some classes of errors through static analysis, and perform optimizations, such as removing branches and null tests.

Of course, you could argue for a non_negative wrapper for integers, which avoids both categories of errors, but that would have its own issues...

The google statement is about using unsigned as a size type for containers. In contrast, the question appears to be more general. Please keep that in mind, while you read on.

Since most answers so far reacted to the google statement, less so to the bigger question, I will start my answer about negative container sizes and subsequently try to convince anyone (hopeless, I know...) that unsigned is good.

Signed container sizes

Lets assume someone coded a bug, which results in a negative container index. The result is either undefined behavior or an exception / access violation. Is that really better than getting undefined behavior or an exception / access violation when the index type was unsigned? I think, no.

Now, there is a class of people who love to talk about mathematics and what is "natural" in this context. How can an integral type with negative number be natural to describe something, which is inherently >= 0? Using arrays with negative sizes much? IMHO, especially mathematically inclined people would find this mismatch of semantics (size/index type says negative is possible, while a negative sized array is hard to imagine) irritating.

So, the only question, remaining on this matter is if - as stated in the google comment - a compiler could actually actively assist in finding such bugs. And even better than the alternative, which would be underflow protected unsigned integers (x86-64 assembly and probably other architectures have means to achieve that, only C/C++ does not use those means). The only way I can fathom is if the compiler automatically added run time checks (if (index < 0) throwOrWhatever) or in case of compile time actions produce a lot of potentially false positive warnings/errors "The index for this array access could be negative." I have my doubts, this would be helpful.

Also, people who actually write runtime checks for their array/container indices, it is more work dealing with signed integers. Instead of writing if (index < container.size()) { ... } you now have to write: if (index >= 0 && index < container.size()) { ... }. Looks like forced labor to me and not like an improvement...

Languages without unsigned types suck...

Yes, this is a stab at java. Now, I come from embedded programming background and we worked a lot with field buses, where binary operations (and,or,xor,...) and bit wise composition of values is literally the bread and butter. For one of our products, we - or rather a customer - wanted a java port... and I sat opposite to the luckily very competent guy who did the port (I refused...). He tried to stay composed... and suffer in silence... but the pain was there, he could not stop cursing after a few days of constantly dealing with signed integral values, which SHOULD be unsigned... Even writing unit tests for those scenarios is painful and me, personally I think java would have been better off if they had omitted signed integers and just offered unsigned... at least then, you do not have to care about sign extensions etc... and you can still interpret numbers as 2s complement.

Those are my 5 cents on the matter.

ReferenceURL : https://stackoverflow.com/questions/51677855/is-using-an-unsigned-rather-than-signed-int-more-likely-to-cause-bugs-why

반응형