programing

C의 숫자가 음수가 아니더라도 항상 'int'를 사용해야 합니까?

newsource 2022. 8. 8. 20:36

C의 숫자가 음수가 아니더라도 항상 'int'를 사용해야 합니까?

음수가 되어서는 안 되는 값에는 항상 부호 없는 int를 사용합니다.하지만 오늘 코드로 다음과 같은 상황을 알게 되었습니다.

void CreateRequestHeader( unsigned bitsAvailable, unsigned mandatoryDataSize, 
    unsigned optionalDataSize )
{
    If ( bitsAvailable – mandatoryDataSize >= optionalDataSize ) {
        // Optional data fits, so add it to the header.
    }

    // BUG! The above includes the optional part even if
    // mandatoryDataSize > bitsAvailable.
}

숫자가 음수가 아니더라도 부호 없는 int 대신 int를 사용해야 합니까?

한 가지 언급되지 않은 것은 서명된/서명되지 않은 번호를 교환하면 보안 버그가 발생할 수 있다는 것입니다.이것은 큰 문제입니다.표준 C라이브러리의 부호 없는 번호(fread, memcpy, malloc 등)를 취득/반환하는 함수의 대부분은 모두 사용되기 때문입니다.size_t파라미터)

예를 들어 다음과 같은 무해한 예를 들어보겠습니다(실제 코드에서).

//Copy a user-defined structure into a buffer and process it
char* processNext(char* data, short length)
{
    char buffer[512];
    if (length <= 512) {
        memcpy(buffer, data, length);
        process(buffer);
        return data + length;
    } else {
        return -1;
    }
}

해가 없어 보이지?문제는 말이다length서명되어 있지만, 에 전달되면 부호 없음으로 변환됩니다.memcpy. 따라서 길이를 다음과 같이 설정합니다.SHRT_MIN유효성을 확인합니다.<= 512테스트, 단 원인memcpy버퍼에 512바이트 이상 복사 - 공격자는 스택의 함수 반환 주소를 덮어쓰고 (약간의 작업 후) 컴퓨터를 탈취할 수 있습니다!

여러분은 순진하게 "길이를 재거나 확인할 필요가 있는 것은 너무나 명백하다, 나는 절대 그런 실수를 할 수 없다."라고 말할지도 모른다.단, 사소하지 않은 글을 써본 적이 있다면 써본 적이 있습니다.Windows, Linux, BSD, Solaris, Firefox, OpenSSL, Safari, MS Paint, Internet Explorer, Google Picasa, Opera, Flash, Open Office, Subversion, Apache, Python, PHP, Pidgin, Gimp, ...의 저자들은 모두 보안을 잘 알고 있습니다.

요컨대, 항상 사이즈에 사용하세요.

프로그래밍은 어려워

내가 항상...

"항상..."에 대한 대답은 "아니오"입니다. 데이터 유형을 사용해야 하는지 여부를 결정하는 요인은 많습니다. 일관성이 중요합니다.

하지만 이것은 매우 주관적인 질문입니다. 서명하지 않은 것을 망치기 쉽습니다.

for (unsigned int i = 10; i >= 0; i--);

무한 루프가 발생합니다.

이것이 구글의 C++ 스타일 가이드를 포함한 일부 스타일 가이드가 낙담하는 이유이다.unsigneddata type.

제 개인적인 의견으로는, 서명되지 않은 데이터 타입의 이러한 문제로 인해 발생하는 버그는 그다지 많지 않습니다.코드를 체크하고 신중하게 사용하기 위해 어설션을 사용하세요(산술 수행 시에도 적게 사용).

부호 없는 정수 유형을 사용해야 하는 경우는 다음과 같습니다.

  • 기준점을 순수 이진 표현으로 처리해야 합니다.
  • 부호 없는 숫자로 얻을 수 있는 모듈로 산술의 의미가 필요합니다.
  • 서명되지 않은 유형(예: 수락/반환하는 표준 라이브러리 루틴)을 사용하는 코드와 인터페이스해야 합니다.size_t가치.

그러나 일반적인 산술에서는, 「부정수가 될 수 없다」라고 말할 때, 반드시 부호 없는 활자를 사용해야 하는 것은 아닙니다.부호 없이 음의 값을 넣을 수 있기 때문에, 그것을 꺼내려고 할 때, 그것은 매우 큰 값이 됩니다.따라서 기본 제곱근 함수와 같이 음수 값이 금지되어 있는 경우에는 함수의 전제 조건을 기술하고 있으므로 단언해야 합니다.또한 불가능하다고 단언할 수 없습니다.대역 외 값을 유지하여 테스트할 수 있는 방법이 필요합니다(이는 같은 종류의 논리입니다).getchar()반환int가 아니라char.)

또한 서명된 vs. 서명되지 않은 vs.의 선택은 성능에도 실질적인 영향을 미칠 수 있습니다.아래의 (제어된) 코드를 참조하십시오.

#include <stdbool.h>

bool foo_i(int a) {
    return (a + 69) > a;
}

bool foo_u(unsigned int a)
{
    return (a + 69u) > a;
}

둘다요.foo는 파라미터의 타입을 제외하고 동일합니다.단, 다음과 같이 컴파일 할 경우c99 -fomit-frame-pointer -O2 -S, 다음과 같은 것이 있습니다.

.file "try.c".본문.p2align 4,15.global foo_i.type foo_i, @functionfoo_i:$1, %eax 이동리트.size foo_i, .-foo_i.p2align 4,15.global foo_u.type foo_u, @functionfoo_u:movl 4(%param), %eax수평 69(%eax), %edxcmpl %eax, %edx세타 %al리트.size foo_u, .-foo_u.ident "GCC: (Debian 4.4-7) 4.4.4".section.주의.GNU 스택", @probits

보이시죠?foo_i()보다 효율적입니다.foo_u()이는 서명되지 않은 산술 오버플로는 표준으로 정의되어 "wrap around"로 정의되기 때문입니다.(a + 69u)보다 작을 가능성이 높다a한다면a매우 크기 때문에 이 케이스에 대한 코드가 있을 것입니다.한편, 서명된 산술 오버플로는 정의되어 있지 않기 때문에, GCC는 서명된 산술이 오버플로하지 않는다고 가정합니다.(a + 69) 보다 작을 수는 없다a. 따라서 서명되지 않은 유형을 무차별적으로 선택하면 성능에 불필요한 영향을 미칠 수 있습니다.

답은 '그렇다'입니다.C 및 C++ 의 「부호 없음」int 타입은, 그 타입의 이름이 어떻게 되어도 「항상 정의 정수」는 아닙니다.타입을 「non-negative」라고 읽으려고 하면, C/C++ 부호 없는 ints 의 동작은 의미가 없습니다.예를 들어 다음과 같습니다.

  • 부호 없는 두 숫자의 차이는 부호 없는 숫자입니다('음수가 아닌 두 숫자의 차이는 음수가 아닙니다'로 읽으면 의미가 없습니다).
  • int 및 부호 없는 int의 추가는 서명되지 않았습니다.
  • int에서 unsigned int로의 암묵적인 변환이 있습니다(부호 없이 "non-negative"로 읽으면 그 반대 변환이 의미가 있습니다).
  • 누군가 음의 int를 전달했을 때 서명되지 않은 파라미터를 받아들이는 함수를 선언하면 암묵적으로 큰 양의 값으로 변환됩니다.즉, 서명되지 않은 파라미터 유형을 사용하는 것은 컴파일 시에도 런타임에도 오류를 찾는 데 도움이 되지 않습니다.

실제로 부호 없는 숫자는 링 "integers-modulo-N"의 요소이며 N은 2의 거듭제곱이기 때문에 경우에 따라 매우 유용합니다.부호 없는 int는 해당 modulo-n 산술 또는 비트마스크로 사용할 때 유용하지만 수량으로는 유용하지 않습니다.

불행히도 C와 C++ 부호 없는 정수는 음수가 아닌 양을 나타내며 정수가 작은 경우 16비트를 모두 사용할 수 있습니다.32k 또는 64k를 사용할 수 있는 것은 큰 차이라고 생각되었습니다.기본적으로 역사적 사고로 분류하고 싶은데...논리가 없다고 해서 논리를 읽으려고 하면 안 돼요.

그건 그렇고, 내 생각에는 그건 실수였어...32k가 충분하지 않다면 64k도 곧 충분치 않을 것입니다; 제 의견으로는 단 하나의 추가 비트 때문에 modulo 정수를 남용하는 것은 비용이 너무 많이 들었습니다.물론 음이 아닌 적절한 유형이 존재하거나 정의되어 있다면 그렇게 하는 것이 합리적이었을 것이다.부호가 없는 의미론은 부정적이지 않은 의미로서 사용하기에는 잘못된 것입니다.

부호가 없는 것이 좋다고 말하는 사람이 있을 수 있습니다. 왜냐하면 부호가 아닌 값만 원하는 "문서화"를 하기 때문입니다.그러나 이 문서는 서명되지 않은 C 또는 C++의 작동 방식을 실제로 모르는 사람들에게만 가치가 있습니다.음이 아닌 값에 사용되는 부호 없는 유형을 보는 것은 코드를 작성한 사람이 그 부분의 언어를 이해하지 못했다는 것을 의미합니다.

부호 없는 int의 「랩핑」동작을 이해하고 싶은 경우는, 올바른 선택입니다(예를 들면, 바이트를 취급할 때는 거의 항상 「서명되지 않은 문자」를 사용합니다).랩핑 동작을 사용하지 않는 경우(그리고 그 동작은, 보여지는 차이의 경우와 같이 문제가 됩니다).ar는 부호 없는 타입이 부적절한 선택이며 플레인 ints를 사용해야 함을 나타냅니다.

즉, C++가std::vector<>::size()반환 유형이 잘못된 선택입니까? 네...그건 실수다.하지만 당신이 그렇게 말한다면 "서명되지 않은" 이름이 단지 이름일 뿐이라는 것을 이해하지 못하는 사람들로부터 욕을 들을 준비를 해야 한다.그것이 중요한 것은 동작이며, 그것은 "modulo-n" 동작입니다(그리고 아무도 컨테이너의 크기에 대해 "modulo-n" 타입을 합리적인 선택이라고 생각하지 않습니다.

여기 있는 대부분의 사람들과는 의견이 다른 것 같지만unsigned활자는 꽤 유용하지만, 원시 역사 형태로는 그렇지 않습니다.

그 결과, 타입이 나타내는 의미에 충실하면, 문제는 없습니다.사용하세요.size_t배열 인덱스, 데이터 오프셋 등에 대해 (설계 해제됨) off_t(서명)을 지정합니다.사용하다ptrdiff_t(부호 있음) 포인터의 차이에 대응합니다.사용하다uint8_t부호 없는 작은 정수의 경우int8_t서명하신 분들을 위해서요.또한 휴대성 문제의 80% 이상을 방지할 수 있습니다.

그리고 사용하지 마세요.int,long,unsigned,char안 그러면 안 돼요.그것들은 이력서에 속합니다.(경우에 따라 오류 반환, 비트 필드 등)

예를 들어 보겠습니다.

bitsAvailable – mandatoryDataSize >= optionalDataSize

쉽게 고쳐 쓸 수 있다

bitsAvailable >= optionalDataSize + mandatoryDataSize

잠재적인 오버플로 문제를 피할 수 없습니다(assert테스트하고 싶은 것에 대해서 조금 더 가까이 다가갈 수 있을 것 같습니다.

C++의 제작자인 Bjarne Stroustrup은 그의 책 C++ 프로그래밍 언어에서 부호 없는 활자를 사용하는 것에 대해 경고합니다.

부호 없는 정수 유형은 저장소를 비트 배열로 처리하는 데 이상적입니다.int 대신 부호 없는 부호를 사용하여 양의 정수를 나타내는 비트를 하나 더 얻는 것은 거의 좋은 생각이 아닙니다.변수를 부호 없이 선언함으로써 일부 값이 양수임을 확인하려는 시도는 일반적으로 암묵적인 변환 규칙에 의해 저지됩니다.

if (bitsAvailable >= optionalDataSize + mandatoryDataSize) {
    // Optional data fits, so add it to the header.
}

mandatoryDataSize + optionalDataSize가 부호 없는 정수형을 오버플로 할 수 없는 한 버그가 없습니다.이러한 변수의 이름을 지정하면 이러한 경우가 발생할 가능성이 높다고 생각할 수 있습니다.

그 상황은(bitsAvailable – mandatoryDataSize)타입이 부호 없는 경우 '예상하지 않은' 결과를 생성합니다.bitsAvailable < mandatoryDataSize데이터가 절대 음이 되지 않을 것으로 예상되는 경우에도 부호 있는 유형이 사용되는 경우가 있습니다.

저는 부정할 이유가 없는 데이터에는 보통 부호 없는 타입을 사용합니다만, 산술적인 랩핑으로 버그가 노출되지 않도록 해야 합니다.

한편, 부호 있는 타입을 사용하는 경우는, 오버플로를 고려할 필요가 있는 경우가 있습니다.

MAX_INT + 1

중요한 것은 이러한 버그에 대해 계산을 할 때 주의해야 한다는 것입니다.

아니요, 용도에 맞는 유형을 사용해야 합니다.황금률이란 없다.소형 마이크로 컨트롤러에서는 예를 들어 8비트 또는 16비트 변수를 사용하는 것이 더 빠르고 메모리 효율적일 수 있습니다. 이는 대부분의 경우 네이티브 데이터패스 크기이지만 매우 특별한 경우입니다.가능한 한 stdint.h 를 사용하는 것도 추천합니다.Visual Studio를 사용하는 경우 BSD 라이센스 버전을 찾을 수 있습니다.

오버플로우 가능성이 있는 경우는, 계산중에 다음의 데이터 타입에 값을 할당합니다.즉, 다음과 같습니다.

void CreateRequestHeader( unsigned int bitsAvailable, unsigned int mandatoryDataSize, unsigned int optionalDataSize ) 
{ 
    signed __int64 available = bitsAvailable;
    signed __int64 mandatory = mandatoryDataSize;
    signed __int64 optional = optionalDataSize;

    if ( (mandatory + optional) <= available ) { 
        // Optional data fits, so add it to the header. 
    } 
} 

그렇지 않으면 값을 계산하지 않고 개별적으로 확인합니다.

void CreateRequestHeader( unsigned int bitsAvailable, unsigned int mandatoryDataSize, unsigned int optionalDataSize ) 
{ 
    if ( bitsAvailable < mandatoryDataSize ) { 
        return;
    } 
    bitsAvailable -= mandatoryDataSize;

    if ( bitsAvailable < optionalDataSize ) { 
        return;
    } 
    bitsAvailable -= optionalDataSize;

    // Optional data fits, so add it to the header. 
} 

표준 라이브러리의 많은 typedef가 서명되지 않았기 때문에 휴대용 코드에서 서명되지 않은 유형을 완전히 피할 수 없습니다.size_t많은 함수가 이를 반환합니다(예:std::vector<>::size()).

하지만, 저는 일반적으로 당신이 개략적으로 설명한 이유로 가능한 한 서명된 유형을 고수하는 것을 선호합니다.이것은 단지 여러분이 제기하는 경우가 아닙니다.서명된 산수와 서명되지 않은 산수의 경우, 서명된 인수는 조용히 서명되지 않은 상태로 승격됩니다.

From the comments on one of Eric Lipperts Blog Posts (See here):

Jeffrey L. Whitledge

I once developed a system in which negative values made no sense as a parameter, so rather than validating that the parameter values were non-negative, I thought it would be a great idea to just use uint instead. I quickly discovered that whenever I used those values for anything (like calling BCL methods), they had be converted to signed integers. This meant that I had to validate that the values didn't exceed the signed integer range on the top end, so I gained nothing. Also, every time the code was called, the ints that were being used (often received from BCL functions) had to be converted to uints. It didn't take long before I changed all those uints back to ints and took all that unnecessary casting out. I still have to validate that the numbers are not negative, but the code is much cleaner!

Eric Lippert

Couldn't have said it better myself. You almost never need the range of a uint, and they are not CLS-compliant. The standard way to represent a small integer is with "int", even if there are values in there that are out of range. A good rule of thumb: only use "uint" for situations where you are interoperating with unmanaged code that expects uints, or where the integer in question is clearly used as a set of bits, not a number. Always try to avoid it in public interfaces. - Eric

You'll need to look at the results of the operations you perform on the variables to check if you can get over/underflows - in your case, the result being potentially negative. In that case you are better off using the signed equivalents.

I don't know if its possible in c, but in this case I would just cast the X-Y thing to an int.

If your numbers should never be less than zero, but have a chance to be < 0, by all means use signed integers and sprinkle assertions or other runtime checks around. If you're actually working with 32-bit (or 64, or 16, depending on your target architecture) values where the most significant bit means something other than "-", you should only use unsigned variables to hold them. It's easier to detect integer overflows where a number that should always be positive is very negative than when it's zero, so if you don't need that bit, go with the signed ones.

Suppose you need to count from 1 to 50000. You can do that with a two-byte unsigned integer, but not with a two-byte signed integer (if space matters that much).

ReferenceURL : https://stackoverflow.com/questions/3259413/should-you-always-use-int-for-numbers-in-c-even-if-they-are-non-negative