현대 C에서 가변 너비 유형이 고정 유형으로 대체 되었습니까?


21

Code Review 에 대한 검토에서 오늘 흥미로운 점을 발견했습니다 . @Veedrac 은이 대답 에서 가변 크기 유형 (예 : intlong)을 uint64_tand 같은 고정 크기 유형으로 대체 할 것을 권장했습니다 uint32_t. 그 답변의 의견에서 인용 :

int와 long의 크기 (따라서 보유 할 수있는 값)는 플랫폼에 따라 다릅니다. 한편, int32_t의 길이는 항상 32 비트입니다. int를 사용한다는 것은 코드가 다른 플랫폼에서 다르게 작동한다는 것을 의미합니다. 일반적으로 원하는 것은 아닙니다.

공통 유형을 수정하지 않는 표준에 대한 추론은 여기 @supercat에 의해 부분적으로 설명 됩니다 . C는 당시 시스템 프로그래밍에 일반적으로 사용되었던 어셈블리와 달리 아키텍처 전반에서 이식 가능하도록 작성되었습니다.

디자인 의도는 원래 int 이외의 각 유형이 다양한 크기의 숫자를 처리 할 수있는 가장 작은 크기이고 +/- 32767을 처리 할 수있는 가장 실용적인 "일반 목적"크기 인 것으로 생각했습니다.

나에 관해서는, 나는 항상 int대안을 사용 하고 걱정하지 않았습니다. 나는 항상 그것이 최고의 성능, 이야기의 끝에서 가장 유형이라고 생각했습니다. 고정 너비가 유용하다고 생각한 유일한 장소는 스토리지 또는 네트워크를 통한 전송을 위해 데이터를 인코딩 할 때입니다. 나는 다른 사람들이 작성한 코드에서 고정 너비 유형을 거의 보지 못했습니다.

70 년대에 갇혀 int있습니까? 아니면 실제로 C99 시대와 그 이후 에 사용하기위한 근거가 있습니까?


1
사람들의 일부는 다른 사람들을 모방합니다. 나는 고정 비트 타입의 코드가 대부분 양심적으로 만들어 졌다고 생각합니다. 크기를 설정하지 않을 이유가 없습니다. 나는 16 비트 플랫폼 (MS-DOS 및 Xenix 80s)을 기본으로 만든 코드를 가지고 있으며, 오늘날 64 개의 새로운 단어 크기와 주소 지정의 이점을 컴파일하고 컴파일하여 컴파일합니다. 즉, 데이터 내보내기 / 가져 오기를위한 직렬화는 이식성을 유지하는 데 매우 중요한 아키텍처 설계입니다.
Luciano

답변:


7

uint32_t저장 프로그래머 와 같은 유형 의 크기에 대해 걱정할 필요 가없는 일반적이고 위험한 신화가 있습니다 int. 표준위원회가 기계 독립적 의미론으로 정수를 선언하는 수단을 정의하는 것이 도움이되지만 서명되지 않은 유형 uint32_t은 의미가 너무 느려서 코드가 깨끗하고 이식 가능한 방식으로 작성 될 수 없습니다. 더욱이, 부호있는 유형 int32은 의미없는 의미를 가지는데, 이는 많은 애플리케이션 방식에 대해 불필요하게 엄격하게 정의되므로 유용한 최적화가 무엇인지 배제합니다.

예를 들어,

uint32_t upow(uint32_t n, uint32_t exponent)
{
  while(exponent--)
    n*=n;
  return n;
}

int32_t spow(int32_t n, uint32_t exponent)
{
  while(exponent--)
    n*=n;
  return n;
}

컴퓨터에 int하나가 4,294,967,295을 길게 할 수 없거나 18,446,744,065,119,617,025 저장할 수, 제 함수의 모든 값에 대해 정의 된 것 nexponent, 그 동작의 크기에 의해 영향을받지 않을 것이다 int; 또한, 표준은 어떤 크기의 컴퓨터에서 다른 동작을 얻을 것을 요구하지 않습니다 int 의 일부 값 nexponentint로서 그러나, 4294967295는 표현할 수 있습니다 시스템에 정의되지 않은 동작을 호출 할 원인이됩니다, int하지만 18,446,744,065,119,617,025가 없습니다.

두 번째 기능은 어떤 값에 대한 정의되지 않은 동작을 얻을 것입니다 nexponent시스템에 int4611686014132420609을 보유 할 수는 없지만 모든 값에 대해 정의 된 동작을 얻을 것 n하고 exponent는 (사양가 할 수있는 모든 시스템에 int32_t기계 곳을에 그 보수 포장 행동을 의미 )보다 작습니다 int.

역사적으로 표준 int에서 upow컴파일러가 overflow in으로 수행해야하는 작업에 대해 아무 말도하지 않았지만 컴파일러는 오버플로 int하지 않을 정도로 큰 것처럼 일관된 결과를 얻었습니다 . 불행히도 일부 최신 컴파일러는 표준에서 요구하지 않는 동작을 제거하여 프로그램을 "최적화"하려고 할 수 있습니다.


3
수동으로 구현하려는 사람은 누구나이 pow코드를 예로 들어 설명하지는 않습니다 exponent=0.
Mark Hurd

1
나는 당신이 접두사 감소 연산자를 접두사가 아닌 접두사로 사용해야한다고 생각합니다. 현재 1 번의 곱셈을하고 있습니다. 예 : exponent=1검사 전에 감소가 수행되기 때문에 검사 ​​전에 감소가 수행되기 때문에 n이 한 번 곱해집니다 ( 즉 --exponent), 곱셈이 수행되지 않고 n 자체가 반환됩니다.
ALXGTV

2
@MarkHurd : 함수가 실제로 계산하는 것은이므로 함수의 이름이 잘못 지정 N^(2^exponent)되었지만 형식 계산 N^(2^exponent)은 종종 지수 함수 계산에 사용되며 mod-4294967296 지수는 두 문자열의 연결 해시 계산과 같은 경우에 유용합니다. 해시는 알려져 있습니다.
supercat 2016 년

1
@ALXGTV :이 기능은 전력 관련 무언가를 계산 한 것을 설명하기위한 것입니다. 실제로 계산하는 것은 N ^ (2 ^ exponent)이며, 이는 N ^ exponent를 효율적으로 계산하는 일부이며 N이 작더라도 실패 할 수 있습니다 ( uint32_t31 의 반복 곱셈은 UB를 산출하지 않지만 효율적입니다) 31 ^ N을 계산하는 방법은 31 ^ (2 ^ N)의 계산을 수반합니다
supercat

나는 이것이 좋은 주장이라고 생각하지 않습니다. 목표는 모든 입력에 대해 정의 된 기능을 현명하게 만들거나 그렇지 않게 만드는 것이 아닙니다. 크기와 오버플로에 대해 추론 할 수 있어야합니다. int32_t때로는 오버플로를 정의했지만 때로는 언급하지 않은 것처럼 보일 수도 있습니다. 처음에 오버플로를 방지하는 이유를 고려할 때 최소한 중요합니다. 정의 된 오버플로를 원한다면 결과 모듈로 고정 값을 원할 가능성이 있습니다. 그래서 고정 너비 유형을 사용하고 있습니다.
Veedrac

4

버퍼 크기, 배열 인덱스 및 Windows '와 같은 포인터 (따라서 주소 지정 가능한 메모리 양)와 밀접하게 관련된 값의 lParam경우 아키텍처 종속 크기의 정수 유형을 갖는 것이 좋습니다. 따라서 가변 크기 유형이 여전히 유용합니다. 이것은 우리가 형식 정의가 왜 size_t, ptrdiff_t, intptr_t, 등 그들은 내장 된 C 타입 정수의 어느 것도 포인터 크기있을 필요가 있기 때문에 형식 정의 할 수 있습니다.

질문은 그래서 정말 여부 char, short, int, long, 그리고 long long여전히 유용하다.

IME는 여전히 C 및 C ++ 프로그램이 int대부분의 용도로 사용 하는 것이 일반적입니다 . 그리고 대부분의 경우 (즉, 숫자가 ± 32 767 범위에 있고 엄격한 성능 요구 사항이없는 경우) 제대로 작동합니다.

그러나 17-32 비트 범위의 숫자 (대도시 인구 등)로 작업해야하는 경우 어떻게해야합니까? 을 사용할 수는 int있지만 플랫폼 종속성을 하드 코딩하는 것입니다. 표준을 엄격히 준수하려면을 사용할 수 long있으며 이는 32 비트 이상이어야합니다.

문제는 C 표준이 정수 유형의 최대 크기를 지정하지 않는다는 것 입니다. long64 비트 구현이 있으며 메모리 사용량이 두 배가됩니다. 그리고 이것들이 long수백만 개의 아이템을 가진 배열의 요소라면, 미친 것처럼 메모리를 쓰러 트릴 것입니다.

따라서 프로그램이 크로스 플랫폼 및 메모리 효율적이기를 원한다면 여기에 사용하기에 적합한 유형 도 int아닙니다 long. 를 입력하십시오 int_least32_t.

  • I16L32 컴파일러는 다음과 같은 long잘림 문제를 피하면서 32 비트를 제공합니다.int
  • I32L64 컴파일러는 int64 비트의 낭비되는 메모리를 피하면서 32 비트를 제공합니다 long.
  • I36L72 컴파일러는 36 비트를 제공합니다 int

OTOH, 엄청난 숫자 나 큰 배열이 필요 하지 않지만 속도 가 필요하다고 가정하십시오 . 그리고 int모든 플랫폼에서 충분한 수 있지만 반드시 가장 빠른 유형이 아닌 수 : 64 비트 시스템은 일반적으로 여전히 32 비트 있습니다 int. 하지만 당신은 사용할 수 있습니다 int_fast16_t그것은 여부는 "빠른"유형을 얻을 int, long또는 long long.

따라서의 유형에 대한 실제 사용 사례가 있습니다 <stdint.h>. 표준 정수 유형 아무 의미 가 없습니다 . 특히 long32 비트 또는 64 비트 일 수 있으며 컴파일러 작성자의 요구에 따라 포인터를 보유하기에 충분히 크거나 크지 않을 수 있습니다.


같은 유형의 문제 uint_least32_t는 다른 유형과의 상호 작용이의 유형보다 훨씬 약하게 지정된다는 것입니다 uint32_t. IMHO 표준은 uwrap32_tand와 같은 유형을 정의해야 하며 unum32_t, 유형 을 정의하는 모든 컴파일러 uwrap32_t는 기본적 int으로 32 비트 인 경우 승격되는 것과 동일한 경우 부호없는 유형으로 승격 unum32_t해야 하며 유형을 정의하는 모든 컴파일러 는 다음을 보장해야합니다. 기본 산술 프로모션은 항상 값을 보유 할 수있는 부호있는 유형으로 변환합니다.
supercat

또한, 표준은 또한 누구의 저장 및 앨리어싱과 호환 있었다 유형 정의 할 수 intN_tuintN_t, 누구의 정의 행동이 될 것이다 일관성intN_tuintN_t있지만, 어떤 컴파일러에게 자신의 범위를 벗어나는 값을 할당하는 경우 코드에서 약간의 자유를 부여하는 것 [했다 그와 유사한 의미를 허용 아마도위한 uint_least32_t를 추가 여부와 같은 불확실성이 있지만없이 uint_least16_t하고는 int32_t서명 또는 usnigned 결과를 얻을 것입니다.
supercat
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.