부호있는 / 부호없는 비교


85

다음 코드가 표시된 장소에서 경고를 표시하지 않는 이유를 이해하려고합니다.

//from limits.h
#define UINT_MAX 0xffffffff /* maximum unsigned int value */
#define INT_MAX  2147483647 /* maximum (signed) int value */
            /* = 0x7fffffff */

int a = INT_MAX;
//_int64 a = INT_MAX; // makes all warnings go away
unsigned int b = UINT_MAX;
bool c = false;

if(a < b) // warning C4018: '<' : signed/unsigned mismatch
    c = true;
if(a > b) // warning C4018: '<' : signed/unsigned mismatch
    c = true;
if(a <= b) // warning C4018: '<' : signed/unsigned mismatch
    c = true;
if(a >= b) // warning C4018: '<' : signed/unsigned mismatch
    c = true;
if(a == b) // no warning <--- warning expected here
    c = true;
if(((unsigned int)a) == b) // no warning (as expected)
    c = true;
if(a == ((int)b)) // no warning (as expected)
    c = true;

배경 프로모션과 관련이 있다고 생각했는데 마지막 두 사람은 그렇지 않은 것 같습니다.

내 생각에, 첫 번째 ==비교는 다른 것만 큼 서명 / 부호없는 불일치입니까?


3
gcc 4.4.2는 '-Wall'로 호출 될 때 경고를 인쇄합니다.
bobah

이것은 추측이지만 컴파일 타임에 답을 알기 때문에 모든 비교를 최적화 할 수 있습니다.
Null Set

2
아! 레. bobah의 의견 : 모든 경고를 켰는데 이제 누락 된 경고가 나타납니다. 나는 그것이 다른 비교와 동일한 경고 수준 설정에서 나타났어 야한다고 생각합니다.
피터

1
@bobah : gcc 4.4.2가 그 경고를 출력한다는 사실이 정말 싫습니다 (불균등에 대해서만 출력하도록 지시 할 방법이 없음) . 그 경고를 침묵시키는 모든 방법이 상황을 악화시키기 때문 입니다. 기본 승격은 -1 또는 ~ 0을 서명되지 않은 유형의 가능한 가장 높은 값으로 안정적으로 변환하지만 경고를 직접 캐스팅하여 경고를 무음 화하는 경우 정확한 유형 을 알아야합니다 . 따라서 유형을 변경하면 (unsigned long long으로 확장), bare와의 비교 -1는 여전히 작동하지만 (경고를 제공함) -1u또는 (unsigned)-1둘 다 비참하게 실패합니다.
Jan Hudec 2011

경고가 필요한 이유와 컴파일러가 작동하지 않는 이유를 모르겠습니다. -1은 음수이므로 부호없는 숫자보다 작습니다. 단순함.
CashCow

답변:


95

부호있는 값과 부호없는 값을 비교할 때 컴파일러는 부호있는 값을 부호없는 값으로 변환합니다. 평등을 위해 이것은 중요하지 않습니다 -1 == (unsigned) -1. 다른 비교의 경우 중요합니다. 예를 들어 다음이 참 -1 > 2U입니다..

편집 : 참조 :

5/9 : (표현식)

산술 또는 열거 유형의 피연산자를 예상하는 많은 이항 연산자는 유사한 방식으로 변환을 일으키고 결과 유형을 산출합니다. 목적은 결과의 유형이기도 한 공통 유형을 생성하는 것입니다. 이 패턴을 일반적인 산술 변환이라고하며 다음과 같이 정의됩니다.

  • 피연산자가 long double 유형이면 다른 피연산자는 long double로 변환됩니다.

  • 그렇지 않으면 피연산자가 double이면 다른 피연산자는 double로 변환됩니다.

  • 그렇지 않으면 피연산자가 float이면 다른 피연산자는 float로 변환됩니다.

  • 그렇지 않으면 적분 프로모션 (4.5)이 두 피연산자 모두에서 수행됩니다 .54)

  • 그런 다음 피연산자가 unsigned long이면 다른 피연산자는 unsigned long으로 변환됩니다.

  • 그렇지 않고 한 피연산자가 long int이고 다른 하나는 unsigned int이면 long int가 unsigned int의 모든 값을 나타낼 수 있으면 unsigned int는 long int로 변환됩니다. 그렇지 않으면 두 피연산자가 unsigned long int로 변환됩니다.

  • 그렇지 않으면 피연산자가 길면 다른 피연산자가 long으로 변환됩니다.

  • 그렇지 않으면 피연산자가 부호가없는 경우 다른 피연산자가 부호없는 것으로 변환됩니다.

4.7 / 2 : (통합 변환)

대상 유형이 부호없는 경우 결과 값은 소스 정수에 합동하는 최소 부호없는 정수입니다 (모듈로 2 n 여기서 n은 부호없는 유형을 나타내는 데 사용되는 비트 수). [참고 : 2의 보수 표현에서이 변환은 개념적이며 비트 패턴에 변화가 없습니다 (잘림이없는 경우). ]

EDIT2 : MSVC 경고 수준

MSVC의 다양한 경고 수준에 대해 경고되는 것은 물론 개발자가 선택한 것입니다. 내가보기에, 부호있는 / 부호없는 평등과 크거나 작은 비교와 관련된 선택이 의미가 있습니다. 이것은 물론 전적으로 주관적입니다.

-1 == -1의미는 다음과 같습니다 -1 == (unsigned) -1. 직관적 인 결과입니다.

-1 < 2 하지 않는 것과 같은 뜻 -1 < (unsigned) 2이 먼저 눈에 덜 직관적이며, IMO에 "이전"경고를받을 자격 -.


서명 된 것을 서명되지 않은 것으로 어떻게 변환 할 수 있습니까? 부호있는 값 -1의 부호없는 버전은 무엇입니까? (signed -1 = 1111, unsigned 15 = 1111, 비트 단위는 같지만 논리적으로 동일하지는 않습니다.)이 변환을 강제하면 작동하지만 컴파일러가 왜 그렇게할까요? 비논리적입니다. 또한 위에서 언급했듯이 경고를 표시했을 때 누락 된 == 경고가 나타 났는데, 이는 내가 말한 내용을 뒷받침하는 것 같습니다.
Peter

1
4.7 / 2에서 말했듯이 부호없는 부호는 2의 보수에 대한 비트 패턴의 변화가 없음을 의미합니다. 컴파일러가이 작업을 수행하는 이유는 C ++ 표준에서 필요합니다. 나는 서로 다른 수준에서 VS의 경고 뒤에있는 이유가 의도하지 않은 표현의 가능성이라고 믿는다. 그리고 나는 그들에게 부호 / 부호없는 평등 비교가 불평등 비교보다 문제가 될 가능성이 "적을 것"이라는 것에 동의 할 것이다. 이것은 물론 주관적입니다. 이것은 VC 컴파일러 개발자가 선택한 것입니다.
에릭

좋아, 거의 얻을 것 같아. 내가 읽는 방식은 컴파일러가 (개념적으로) 'if (((unsigned _int64) 0x7fffffff) == ((unsigned _int64) 0xffffffff))'입니다. _int64는 0x7fffffff와 0xffffffff를 모두 나타낼 수있는 가장 작은 유형이기 때문입니다. 서명되지 않은 용어로?
피터

2
실제로 (unsigned)-1또는 과 비교하거나과 비교하는 것보다 -1u종종 더 나쁩니다-1 . 즉 때문이다 (unsigned __int64)-1 == -1,하지만 (unsigned __int64)-1 != (unsigned)-1. 따라서 컴파일러가 경고를 표시하면 unsigned 또는 using으로 캐스트하여 침묵을 시도 -1u하고 값이 실제로 64 비트가되거나 나중에 값을 변경하면 코드가 손상됩니다! 그리고 그 기억 size_t부호이며, 64 비트 플랫폼 만 -1을 사용하여 유효하지 않은 값에 대한 64 비트는 매우 일반적이다.
Jan Hudec 2011

1
아마도 cpmpilers는 그렇게해서는 안됩니다. 부호있는 값과 부호없는 값을 비교하는 경우 부호있는 값이 음수인지 확인하십시오. 그렇다면 서명되지 않은 것보다 작은 것이 보장됩니다.
CashCow

32

서명 된 / 서명되지 않은 경고가 중요하고 프로그래머가주의를 기울여야하는 이유는 다음 예제에서 설명합니다.

이 코드의 출력이 맞습니까?

#include <iostream>

int main() {
        int i = -1;
        unsigned int j = 1;
        if ( i < j ) 
            std::cout << " i is less than j";
        else
            std::cout << " i is greater than j";

        return 0;
}

산출:

i is greater than j

놀랐나요? 온라인 데모 : http://www.ideone.com/5iCxY

결론 : 비교해 보면 한 피연산자가 unsigned이면 다른 피연산자는 해당 유형이 부호 unsigned 있으면 암시 적으로 변환됩니다 !


2
그가 맞아! 멍청하지만 그가 옳다. 이것은 내가 전에 본 적이없는 중요한 문제입니다. 부호없는 값을 (더 큰) 부호있는 값으로 변환하지 않는 이유는 무엇입니까?! "if (i <((int) j))"를 수행하면 예상대로 작동합니다. "if (i <((_int64) j))"가 더 의미가 있지만 (할 수 없다고 가정하면 _int64가 int 크기의 두 배라고 가정).
피터

6
@Peter "왜 unsgiend를 (더 큰) 부호있는 값으로 변환하지 않습니까?" 대답은 간단합니다. 더 큰 부호있는 값이 없을 수도 있습니다. 32 비트 머신에서 오래 전에 int와 long은 모두 32 비트 였고 더 큰 것은 없었습니다. 부호있는 것과 부호없는 것을 비교할 때 초기 C ++ 컴파일러는 둘 다 부호있는 것으로 변환되었습니다. 나는 어떤 이유를 잊었 기 때문에 C 표준위원회가 이것을 변경했습니다. 최선의 해결책은 가능한 한 서명되지 않은 것을 피하는 것입니다.
James Kanze 2011 년

5
@JamesKanze : 서명 된 오버플로의 결과는 정의되지 않은 동작이지만 부호없는 오버플로의 결과는 그렇지 않으므로 부호없는 큰 값을 음의 부호로 변환하는 동안 음의 부호있는 값을 부호없는 값으로 변환하는 것이 정의된다는 사실을 가지고 무언가를해야한다고 생각합니다. 값이 아닙니다 .
Jan Hudec 2011

2
@James 컴파일러는 더 큰 유형으로 캐스팅하지 않고도이 비교의보다 직관적 인 의미를 구현하는 어셈블리를 항상 생성 할 수 있습니다. 이 특정 예에서는 먼저 i<0. 그럼 확실히 i보다 작습니다 j. i가 0보다 작지 않은 경우 ìunsigned로 안전하게 변환하여 j. 물론 부호있는 것과 부호없는 것 사이의 비교는 더 느리지 만 그 결과는 어떤 의미에서는 더 정확합니다.
Sven

@Sven 동의합니다. 표준은 두 유형 중 하나로 변환하는 대신 모든 실제 값에 대해 작동하도록 비교를 요구했을 수 있습니다. 그러나 이것은 비교를 위해서만 작동합니다. 나는위원회가 비교 및 ​​기타 작업에 대해 다른 규칙을 원하지 않는다고 생각합니다 (실제로 비교되는 유형이 존재하지 않을 때 비교를 지정하는 문제를 공격하고 싶지 않았습니다).
James Kanze 2014 년

4

== 연산자는 비트 비교를 수행합니다 (단순 나눗셈으로 0인지 확인).

비교보다 작거나 큰 것은 숫자의 부호에 훨씬 더 많이 의존합니다.

4 비트 예 :

1111 = 15? 또는 -1?

그래서 만약 당신이 1111 <0001 ... 그것은 모호합니다 ...

하지만 만약 당신이 1111 == 1111을 가지고 있다면 ... 그것은 당신이 그것을 의미하지는 않았지만 같은 것입니다.


나는 이것을 이해하지만 내 질문에 대답하지 않습니다. 지적했듯이 기호가 일치하지 않으면 1111! = 1111입니다. 컴파일러는 유형의 불일치가 있음을 알고 있는데 왜 경고하지 않습니까? (제 요점은 내 코드에 경고를받지 않는 많은 불일치가 포함될 수 있다는 것입니다.)
Peter

디자인 된 방식입니다. 동등성 테스트는 유사성을 확인합니다. 그리고 비슷합니다. 나는 이것이 이러면 안된다는 것에 동의합니다. 당신은 매크로 나 과부하가 X == y는 될 것이 무엇인가를 할 수있는 ((X <Y) || (X> Y))!
Yochai 티 메르

1

2- 보체 (최신 프로세서)를 사용하여 값을 나타내는 시스템에서는 이진 형식에서도 동일합니다. 이것이 컴파일러가 a == b 에 대해 불평하지 않는 이유 일 수 있습니다 .

그리고 나에게는 이상한 컴파일러가 a == ((int) b) 에 대해 경고하지 않습니다 . 정수 잘림 경고 또는 무언가를 제공해야한다고 생각합니다.


1
C / C ++의 철학은 다음과 같습니다. 컴파일러는 형식간에 명시 적으로 변환 할 때 개발자가 자신이 무엇을하는지 알고 있다고 신뢰합니다. 따라서 경고가 없습니다 (적어도 기본적으로 경고 수준이 기본값보다 높게 설정되면 경고를 생성하는 컴파일러가 있다고 생각합니다).
Péter Török 2011 년

0

Microsoft 가이 경우를 처리하기 위해 다른 경고 번호 (예 : C4389 )를 사용했고 C4389는 기본적으로 (즉, 수준 3) 활성화되지 않았기 때문에 문제의 코드 줄은 C4018 경고를 생성하지 않습니다 .

C4389에 대한 Microsoft 문서 에서 :

// C4389.cpp
// compile with: /W4
#pragma warning(default: 4389)

int main()
{
   int a = 9;
   unsigned int b = 10;
   if (a == b)   // C4389
      return 0;
   else
      return 0;
};

다른 답변은 Microsoft가 항등 연산자에서 특별한 경우를 만들기로 결정한 이유를 잘 설명했지만 C4389 또는 Visual Studio에서 활성화하는 방법을 언급하지 않고는 그 답변이별로 도움이되지 않는다는 것을 알았 습니다 .

또한 C4389를 활성화하려는 경우 C4388 활성화를 고려할 수도 있습니다. 불행히도 C4388에 대한 공식 문서는 없지만 다음과 같은 표현으로 팝업되는 것 같습니다.

int a = 9;
unsigned int b = 10;
bool equal = (a == b); // C4388
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.