C에서 부호없는 변환에 서명-항상 안전합니까?


135

다음 C 코드가 있다고 가정하십시오.

unsigned int u = 1234;
int i = -5678;

unsigned int result = u + i;

어떤 암시 적 변환이 진행되고 있으며이 코드는 u및의 모든 값에 대해 안전 i합니까? ( 이 예제의 결과 가 엄청난 양의 숫자로 오버플로 되지만 int로 다시 캐스팅 하고 실제 결과를 얻을 수 있다는 점에서 안전합니다.)

답변:


223

짧은 답변

귀하가 i된다 변환 부가함으로써 부호없는 정수로 UINT_MAX + 1, 그 첨가는 큰 결과 부호없는 값으로 수행한다 result(의 값에 의존 u하고 i).

긴 답변

C99 표준에 따르면 :

6.3.1.8 일반적인 산술 변환

  1. 두 피연산자의 유형이 모두 같으면 더 이상 변환 할 필요가 없습니다.
  2. 그렇지 않으면 두 피연산자 모두 부호있는 정수 유형을 갖거나 둘 다 부호없는 정수 유형을 갖는 경우, 정수 변환 순위가 낮은 유형의 피연산자는 순위가 더 큰 피연산자의 유형으로 변환됩니다.
  3. 그렇지 않으면 부호없는 정수 유형을 가진 피연산자가 다른 피연산자의 유형 순위보다 크거나 같은 경우 부호있는 정수 유형을 가진 피연산자는 부호없는 정수 유형을 가진 피연산자의 유형으로 변환됩니다.
  4. 그렇지 않으면 부호있는 정수 유형의 피연산자 유형이 부호없는 정수 유형의 피연산자 유형의 모든 값을 나타낼 수 있으면 부호없는 정수 유형의 피연산자는 부호있는 정수 유형의 피연산자 유형으로 변환됩니다.
  5. 그렇지 않으면 두 피연산자가 부호있는 정수 유형의 피연산자 유형에 해당하는 부호없는 정수 유형으로 변환됩니다.

귀하의 경우, 서명되지 않은 int ( u) 및 signed int ( i)가 있습니다. 위의 (3)을 참조하면 두 피연산자가 모두 같은 순위를 가지므로 부호없는 정수 i변환 해야합니다 .

6.3.1.3 부호있는 정수와 부호없는 정수

  1. 정수 유형의 값이 _Bool 이외의 다른 정수 유형으로 변환 될 때 새 유형으로 값을 표시 할 수 있으면 변경되지 않습니다.
  2. 그렇지 않으면 새 유형에 부호가없는 경우 값이 새 유형의 범위에 올 때까지 새 유형에 표시 될 수있는 최대 값보다 하나 이상을 반복적으로 더하거나 빼서 값이 변환됩니다.
  3. 그렇지 않으면 새 유형이 서명되고 값을 표시 할 수 없습니다. 결과는 구현 정의되거나 구현 정의 신호가 발생합니다.

이제 위의 (2)를 참조해야합니다. 귀하는 i추가하여 부호없는 값으로 변환됩니다 UINT_MAX + 1. 따라서 결과는 UINT_MAX구현 에 어떻게 정의되어 있는지에 달려 있습니다. 크기는 크지 만 오버플로는 발생하지 않습니다.

6.2.5 (9)

부호없는 피연산자가 포함 된 계산은 결과 부호없는 정수 유형으로 표현할 수없는 결과가 결과 유형으로 표현할 수있는 가장 큰 값보다 1이 큰 모듈로 감소되므로 오버 플로우 할 수 없습니다.

보너스 : 산술 변환 Semi-WTF

#include <stdio.h>

int main(void)
{
  unsigned int plus_one = 1;
  int minus_one = -1;

  if(plus_one < minus_one)
    printf("1 < -1");
  else
    printf("boring");

  return 0;
}

이 링크를 사용하여 온라인으로 시도 할 수 있습니다 : https://repl.it/repls/QuickWhimsicalBytes

보너스 : 산술 변환 부작용

산술 변환 규칙을 사용 UINT_MAX하여 부호없는 값을로 초기화 하여 값을 얻을 수 있습니다 -1.

unsigned int umax = -1; // umax set to UINT_MAX

위에서 설명한 변환 ​​규칙으로 인해 시스템의 부호있는 숫자 표시에 관계없이 이식 가능합니다. 자세한 내용은이 SO 질문을 참조하십시오. -1을 사용하여 모든 비트를 true로 설정하는 것이 안전합니까?


나는 그것이 절대적 가치를 단순히 할 수없는 이유를 이해하지 못하고 양수와 마찬가지로 부호없는 것으로 취급합니까?
호세 Salvatierra

7
@ D.Singh 친절하게 답변 내의 잘못된 부분을 가리킬 수 있습니까?
Shmil The Cat

부호있는 것을 부호없는 것으로 변환하기 위해 부호없는 값의 최대 값을 추가합니다 (UINT_MAX +1). 마찬가지로 unsigned에서 signed로 변환하는 쉬운 방법은 무엇입니까? 주어진 값을 최대 값에서 빼야합니까 (부호없는 문자의 경우 256)? 예 : 부호있는 숫자로 변환 할 때 140은 -116이됩니다. 그러나 20은 20이됩니다. 여기 쉬운 트릭이 있습니까?
Jon Wheelock


24

부호있는 부호를 부호없는 부호로 변환한다고해서 반드시 부호있는 값의 표현을 복사하거나 해석하는 것은 아닙니다 . C 표준 인용 (C99 6.3.1.3) :

정수 유형의 값이 _Bool 이외의 다른 정수 유형으로 변환 될 때 새 유형으로 값을 표시 할 수 있으면 변경되지 않습니다.

그렇지 않으면 새 유형에 부호가없는 경우 값이 새 유형의 범위에 올 때까지 새 유형에 표시 될 수있는 최대 값보다 하나 이상을 반복적으로 더하거나 빼서 값이 변환됩니다.

그렇지 않으면 새 유형이 서명되고 값을 표시 할 수 없습니다. 결과는 구현 정의되거나 구현 정의 신호가 발생합니다.

요즘 거의 보편적 인 2의 보수 표현의 경우 규칙은 비트를 해석하는 것과 일치합니다. 그러나 다른 표현 (부호 및 크기 또는 1의 보수)의 경우 C 구현은 여전히 ​​동일한 결과를 정렬해야하므로 변환이 비트를 복사 할 수는 없습니다. 예를 들어, 표현에 관계없이 (부호없는) -1 == UINT_MAX입니다.

일반적으로 C의 변환은 표현이 아닌 값에서 작동하도록 정의됩니다.

원래 질문에 대답하려면 :

unsigned int u = 1234;
int i = -5678;

unsigned int result = u + i;

i 값은 unsigned int로 변환되어을 산출 UINT_MAX + 1 - 5678합니다. 그런 다음이 값은 부호없는 값 1234에 추가되어을 산출 UINT_MAX + 1 - 4444합니다.

부호없는 오버플로와 달리 부호있는 오버플로는 정의되지 않은 동작을 호출합니다. 랩 어라운드가 일반적이지만 C 표준에 의해 보장되지는 않으며 컴파일러 최적화는 보증하지 않는 가정을하는 코드를 혼란스럽게 만들 수 있습니다.


5

성경을 참조 하십시오 :

  • 추가 작업으로 인해 int가 부호없는 int로 변환됩니다.
  • 2의 보수 표현과 동일한 크기의 유형을 가정하면 비트 패턴은 변경되지 않습니다.
  • unsigned int에서 signed int 로의 변환은 구현에 따라 다릅니다. (하지만 요즘 대부분의 플랫폼에서 예상대로 작동합니다.)
  • 크기가 다른 부호있는 부호와 부호없는 부호를 결합하는 경우 규칙이 조금 더 복잡합니다.

3

하나의 부호없는 변수와 하나의 부호있는 변수가 추가되거나 모든 이진 연산이 암시 적으로 부호없는 것으로 변환되면 큰 결과가 발생합니다.

따라서 결과가 거대하고 잘못 될 수 있다는 점에서 안전하지만 결코 충돌하지는 않습니다.


사실이 아니다. 6.3.1.8 일반적인 산술 변환 int와 unsigned char를 합하면 후자는 int로 변환됩니다. 부호없는 두 문자를 합하면 int로 변환됩니다.
2501

3

부호있는 것을 부호없는 것으로 변환 할 때 두 가지 가능성이 있습니다. 원래 양수인 숫자는 같은 값으로 유지됩니다 (또는 해석 된 것으로 간주 됨). 원래 음수 인 숫자는 이제 더 큰 양수로 해석됩니다.


1

이전에 답변 한 것처럼 문제없이 서명 된 서명과 서명되지 않은 서명을주고받을 수 있습니다. 부호있는 정수의 경계는 -1 (0xFFFFFFFF)입니다. 그것에서 더하기와 빼기를 시도해보십시오. 당신은 그것을 되돌릴 수 있고 올바른 것을 알 수 있습니다.

그러나 앞뒤로 캐스팅하려는 경우 변수의 이름을 명확하게 지정하여 변수의 이름을 지정하는 것이 좋습니다.

int iValue, iResult;
unsigned int uValue, uResult;

더 중요한 문제에 산만 해져 힌트없이 이름이 지정된 변수가 어떤 유형인지 잊어 버리는 것은 너무 쉽습니다. 부호없는 것으로 캐스팅하고 배열 인덱스로 사용하고 싶지 않습니다.


0

어떤 암묵적 전환이 일어나고 있습니까?

나는 부호없는 정수로 변환됩니다.

이 코드는 u와 i의 모든 값에 안전합니까?

잘 정의 된 의미에서 안전합니다 ( https://stackoverflow.com/a/50632/5083516 참조 ).

규칙은 일반적으로 읽기 어려운 표준 언어로 작성되지만 부호있는 정수에 사용 된 표현은 부호없는 정수에 2의 보수 표현이 포함됩니다.

더하기, 빼기 및 곱하기는이 숫자에서 올바르게 작동하여 "실제 결과"를 나타내는 2의 보수 숫자를 포함하는 부호없는 다른 정수를 만듭니다.

더 큰 부호없는 정수 유형으로 나누기와 캐스트는 잘 정의 된 결과를 갖지만 그 결과는 "실제 결과"의 2의 보수 표현이 아닙니다.

(이 예제의 결과가 엄청난 양의 숫자로 오버플로 될지라도 int로 다시 캐스팅하여 실제 결과를 얻을 수 있다는 점에서 안전합니다.)

부호있는 부호에서 부호없는 것으로의 변환은 표준에 의해 정의되지만 그 반대는 구현 정의 gcc와 msvc는 부호없는 정수에 저장된 2의 보수를 부호있는 정수로 다시 변환 할 때 "실제 결과"를 얻도록 변환을 정의합니다 . 부호있는 정수에 2의 보수를 사용하지 않는 모호한 시스템에서만 다른 동작을 찾을 것으로 기대합니다.

https://gcc.gnu.org/onlinedocs/gcc/Integers-implementation.html#Integers-implementation https://msdn.microsoft.com/en-us/library/0eex498h.aspx


-17

끔찍한 답변

오즈 구르 오시 치탁

부호가있는 부호에서 부호가없는 부호로 (또는 그 반대로) 변환 할 때 번호의 내부 표현은 변경되지 않습니다. 변경 사항은 컴파일러가 부호 비트를 해석하는 방법입니다.

이것은 완전히 잘못되었습니다.

매트 프레드릭 손

하나의 부호없는 변수와 하나의 부호있는 변수가 추가되거나 모든 이진 연산이 암시 적으로 부호없는 것으로 변환되면 큰 결과가 발생합니다.

이것도 잘못되었습니다. 부호없는 정수는 부호없는 유형의 패딩 비트로 인해 동일한 정밀도를 가지면 정수로 승격 될 수 있습니다.

smh

추가 작업으로 인해 int가 부호없는 int로 변환됩니다.

잘못된. 어쩌면 그렇지 않을 수도 있습니다.

unsigned int에서 signed int 로의 변환은 구현에 따라 다릅니다. (하지만 요즘 대부분의 플랫폼에서 예상대로 작동합니다.)

잘못된. 오버플로가 발생하거나 값이 유지되면 정의되지 않은 동작입니다.

익명

i의 값은 unsigned int로 변환됩니다 ...

잘못된. 부호없는 int에 상대적인 int의 정밀도에 따라 다릅니다.

테일러 가격

이전에 답변 한 것처럼 문제없이 서명 된 서명과 서명되지 않은 서명을주고받을 수 있습니다.

잘못된. 부호있는 정수 범위 밖의 값을 저장하려고하면 정의되지 않은 동작이 발생합니다.

이제 마지막으로 질문에 대답 할 수 있습니다.

int의 정밀도가 unsigned int와 같으면 u는 부호있는 int로 승격되고 식 (u + i)에서 -4444 값을 얻습니다. 이제 u와 i에 다른 값이 있으면 오버플로와 정의되지 않은 동작이 발생할 수 있지만 정확한 숫자를 사용하면 -4444 [1]이 됩니다. 이 값은 int 유형입니다. 그러나 그 값을 부호없는 int에 저장하려고하면 부호없는 int로 캐스팅되고 결과는 (UINT_MAX + 1)-4444가됩니다.

부호없는 int의 정밀도가 int의 정밀도보다 클 경우 부호있는 int는 부호없는 int로 승격되어 값 (UINT_MAX + 1)-5678을 산출하고 다른 부호없는 int 1234에 추가됩니다. u와 i가 있어야합니다. 식이 {0..UINT_MAX} 범위를 벗어나는 다른 값은 결과 DOES가 {0..UINT_MAX 범위 내에있을 때까지 값 (UINT_MAX + 1)을 더하거나 빼고 정의되지 않은 동작이 발생하지 않습니다. .

정밀도는 무엇입니까?

정수에는 패딩 비트, 부호 비트 및 값 비트가 있습니다. 부호없는 정수에는 분명히 부호 비트가 없습니다. 부호없는 문자는 패딩 비트가 없도록 보장됩니다. 정수가 갖는 값의 비트 수는 정밀도가 얼마나되는지입니다.

[Gotchas]

패딩 비트가 존재하는 경우 매크로 단독의 매크로 크기는 정수의 정밀도를 결정하는 데 사용될 수 없습니다. 그리고 바이트의 크기는 C99에 의해 정의 된 8 진수 (8 비트) 일 필요는 없습니다.

[1] 두 지점 중 하나에서 오버플로가 발생할 수 있습니다. 추가하기 전에 (프로모션 중)-서명되지 않은 int가있는 경우 int에 맞지 않습니다. 부호없는 int가 int 범위 내에 있더라도 추가 후 오버플로가 발생할 수 있으며 추가 후에도 여전히 오버플로가 발생할 수 있습니다.


6
"서명되지 않은 정수는 정수로 승격 될 수 있습니다." 사실이 아니다. 유형이 이미 순위> = int 이므로 정수 승격이 발생 하지 않습니다 . 6.3.1.1 : "부호없는 정수 유형의 순위는 해당하는 경우 부호있는 정수 유형의 순위와 같아야합니다." 6.3.1.8 : "그렇지 않으면 부호없는 정수 유형을 가진 피연산자가 다른 피연산자의 유형 순위보다 크 거나 같은 경우 부호있는 정수 유형을 가진 피연산자는 부호없는 정수를 가진 피연산자의 유형으로 변환됩니다. 유형." 둘 다 일반적인 산술 변환이 적용될 때 int변환되는 것을 보증합니다 unsigned int.
CB Bailey

1
6.3.1.8 정수 승격 후에 만 ​​발생합니다. 시작 단락에는 "그렇지 않으면 정수 승격이 두 피연산자 모두에서 수행됩니다. 그런 다음 승격 된 피연산자에 다음 규칙이 적용됩니다"라고 말합니다. 승격 규칙 6.3.1.1 ... "정수 변환 순위가 int 및 unsigned int의 순위보다 작거나 같은 정수 유형의 오브젝트 또는 표현식"및 "int가 모든 값을 나타낼 수있는 경우"를 읽으십시오. 원래 유형 인 경우 값이 int로 변환됩니다. "
Elite Mx

1
6.3.1.1 정수 승격은 정수가 아닌 일부 정수 유형 int또는 unsigned int유형이 unsigned int있거나 int예상되는 유형 중 하나로 변환하는 데 사용됩니다 . 은 "또는 동등"은 변환 계수의 열거 된 종류를 허용 TC2 첨가 하였다는 동일 int또는 unsigned int해당 유형 중 하나로 변환한다. 그것은 승진 사이의 변환 것이라고 설명한다는 계획이없는 unsigned intint. 사이의 일반적인 유형 결정 unsigned int과는 int여전히 6.3.1.8, 심지어 포스트 TC2의 적용을받습니다.
CB Bailey

19
비판 다른 사람의 오답 동안 잘못된 답변을 게시하는 작업 ... ;-) 얻기위한 좋은 전략 같은 소리하지 않습니다
R .. GitHub의 STOP 돕기 ICE

6
오만과 결합 된이 수준의 잘못이 너무 재미있어서 삭제에 투표하지 않습니다
MM
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.