size_t 대 uintptr_t


246

C 표준 size_t은 모든 배열 색인을 보유 할 수있는 유형임을 보증합니다 . 즉, 논리적으로 size_t모든 포인터 유형을 보유 할 수 있어야합니다. Google에서 찾은 일부 사이트에서 이것이 합법적이며 항상 작동해야한다는 것을 읽었습니다.

void *v = malloc(10);
size_t s = (size_t) v;

따라서 C99에서 표준은 intptr_tuintptr_t유형을 도입했으며 서명 된 유형과 서명되지 않은 유형은 포인터를 보유 할 수 있습니다.

uintptr_t p = (size_t) v;

사용 size_t과 의 차이점은 무엇 uintptr_t입니까? 둘 다 부호가 없으며 둘 다 포인터 유형을 보유 할 수 있어야 기능적으로 동일하게 보입니다. 선명도 가 아닌을 사용하는 것보다 uintptr_t더 강력한 이유가 있습니까? 내부 기능으로 만 필드를 처리하는 불투명 한 구조에서이를 수행하지 않을 이유가 있습니까?void *size_t

같은 토큰으로, ptrdiff_t포인터 차이를 보유 할 수있는 부호있는 유형이므로 대부분의 포인터를 보유 할 수 있습니다. 그래서 어떻게 구별 intptr_t됩니까?

이 모든 유형이 기본적으로 동일한 기능의 사소한 다른 버전을 제공하지는 않습니까? 그렇지 않다면 왜? 다른 것으로는 할 수없는 것 중 하나로 무엇을 할 수 없습니까? 그렇다면 왜 C99가 본질적으로 불필요한 두 가지 유형을 언어에 추가 했습니까?

함수 포인터는 현재 문제에 적용되지 않기 때문에 기꺼이 무시하고 싶지만 몰래 의심을 가짐에 따라 함수 포인터는 "올바른"답변의 중심이 될 것입니다.

답변:


236

size_t모든 배열 색인을 보유 할 수있는 유형입니다. 이는 논리적으로 size_t가 모든 포인터 유형을 보유 할 수 있어야 함을 의미합니다.

반드시 그런 것은 아닙니다! 예를 들어, 세그먼트 화 된 16 비트 아키텍처의 시대로 돌아가십시오. 어레이는 단일 세그먼트로 제한 될 수 있으므로 (16 비트 size_t는 가능) 여러 세그먼트를 가질 수 있습니다 (따라서 32 비트 intptr_t유형을 선택해야 함) 세그먼트 및 그 안의 오프셋). 요즘에는 균일하게 처리 할 수있는 세그먼트 화되지 않은 아키텍처에서 이런 일이 이상하게 들리지만, 표준은 "2009 년의 일반적인 것"보다 더 넓은 범위를 충족해야한다는 것을 알고 있습니다!-)


6
이 같은 결론에 뛰어 수많은 다른 사람들과 함께 사이의 차이를 설명 size_t하고 uintptr_t있지만 무엇인지 ptrdiff_tintptr_t-이 두 거의 모든 플랫폼에서 동일한 값 범위를 저장 할 수 없을 것이다? 특히 부호있는 포인터 크기의 정수 유형 ptrdiff_t의 목적을 제공하는 경우 부호있는 및 부호없는 포인터 크기의 정수 유형을 모두 갖는 이유는 무엇입니까?
Chris Lutz

8
핵심은 " 거의 모든 플랫폼에서"@Chris입니다. 구현은 포인터를 0xf000-0xffff 범위로 자유롭게 제한 할 수 있습니다. 16 비트 intptr_t가 필요하지만 12/13 비트 ptrdiff_t 만 필요합니다.
paxdiablo

29
@Chris, 같은 배열 내의 포인터에 대해서만 차이를 취하는 것이 잘 정의되어 있습니다. 따라서 정확히 동일한 세그먼트 화 된 16 비트 아키텍처에서 (어레이는 단일 세그먼트 내에 있어야하지만 두 개의 다른 배열은 다른 세그먼트에있을 수 있음) 포인터는 4 바이트 여야하지만 포인터 차이 는 2 바이트 일 수 있습니다!
Alex Martelli

6
@AlexMartelli : 포인터 차이가 양수이거나 음수 일 수 있습니다. 표준은 size_t16 비트 이상이어야하지만 ptrdiff_t17 비트 이상이어야합니다 (실제로 32 비트 이상일 것임).
Keith Thompson

3
x86-64와 같은 현대식 아키텍처는 어떻습니까? 이 아키텍처의 초기 구현은 48 비트 주소 지정 가능 공간 만 제공하지만 포인터 자체는 64 비트 데이터 유형입니다. 합리적으로 처리 할 수있는 가장 큰 연속 메모리 블록은 48 비트이므로 SIZE_MAX2 ** 64가 아니어야합니다. 이것은 간단한 주소 지정을 사용하고 있습니다. SIZE_MAX데이터 포인터의 범위와 데이터 포인터의 범위가 불일치하기 위해 분할이 필요하지 않습니다 .
Andon M. Coleman

89

당신의 진술에 관하여 :

"C 표준 size_t은 모든 배열 색인을 보유 할 수있는 유형을 보장합니다 . 이는 논리적으로 size_t모든 포인터 유형을 보유 할 수 있어야 함을 의미합니다 ."

이것은 실제로 오류입니다 (잘못된 추론으로 인한 오해) (a) . 후자는 전자에서 나온다고 생각할 수도 있지만 실제로는 그렇지 않습니다.

포인터와 배열 인덱스는 동일 하지 않습니다 . 배열을 65536 요소로 제한하지만 포인터가 거대한 128 비트 주소 공간으로 모든 값을 지정할 수 있도록하는 적합한 구현을 예상하는 것은 그럴듯합니다.

C99는 size_t변수 의 상한 이 정의되며 SIZE_MAX65535만큼 낮을 수 있다고 명시합니다 (C11에서 변경되지 않은 C99 TR3, 7.18.3 참조). 현대 시스템에서이 범위로 제한되면 포인터가 상당히 제한됩니다.

실제로, 당신은 아마도 당신의 가정이 적용된다는 것을 알게 될 것입니다. 그러나 그것은 표준이 그것을 보장하기 때문이 아닙니다. 실제로 보장 하지 않기 때문입니다.


(a) 이것은 어떤 형태의 개인적인 공격 이 아니며 , 비판적 사고의 맥락에서 왜 당신의 진술이 잘못된 것인지를 진술하는 것입니다. 예를 들어 다음과 같은 추론도 유효하지 않습니다.

모든 강아지가 귀엽다. 이거 귀여워 그러므로 이것은 강아지 여야합니다.

강아지의 귀여움이나 다른 점은 여기에 아무런 관련이 없습니다. 내가 말하고있는 것은 두 가지 사실이 결론으로 ​​이어지지 않는다는 것입니다. 첫 번째 두 문장은 강아지 가 아닌 귀여운 것들의 존재를 허용하기 때문입니다 .

이것은 반드시 두 번째 명령을 요구하지는 않는 첫 번째 진술과 유사합니다.


오히려 내가 알렉스 마르 텔리의 의견에 말을 다시 입력보다는, 그냥 설명에 대한 감사를 말하지만, 내 질문의 두 번째 절반합니다 (반복하겠습니다 ptrdiff_t대의 intptr_t부분).
Chris Lutz

5
@Ivan은 대부분의 커뮤니케이션과 마찬가지로 특정 기본 항목에 대한 이해가 필요합니다. 이 답변이 "즐거운 재미"로 보이면 내 의도를 오해하고 있음을 확신합니다. 당신이 내 '논리적 오류'의견을 언급한다고 가정하면 (다른 가능성은 볼 수 없습니다), 이것은 OP를 희생하여 작성된 진술이 아니라 사실 진술로 의미되었습니다. 일반적인 불만이 아닌 오해의 가능성을 최소화하기 위해 구체적인 개선 을 제안하고 싶다면 기꺼이 고려해 보시기 바랍니다.
paxdiablo 2016 년

1
@ivan_pozdeev-그것은 독창적이고 과감한 편집의 한 쌍이며, paxdiablo가 누구에게나“재미있게 놀”다는 증거는 없습니다. 내가 OP라면, 나는 이것을 바로 되돌릴 것이다 ....
ex nihilo

1
@Ivan은 제안한 편집 내용이 마음에 들지 않아 롤백했으며 의도하지 않은 위반 사항도 제거하려고했습니다. 제공 할 다른 변경 사항이 있으면 채팅을 시작하여 논의 할 수 있도록 제안합니다.
paxdiablo

1
@ paxdiablo 알았어, "이것은 실제로 오류이다"는 덜 애용하는 것 같아요.
ivan_pozdeev 5

36

세그먼트 제한, 이국적인 아키텍처 등의 추론과 관련하여 다른 모든 답변을 스스로 설명하겠습니다.

이름 의 단순한 차이가 올바른 것에 적절한 유형을 사용하기에 충분하지 않습니까?

크기를 저장하는 경우을 사용하십시오 size_t. 포인터를 저장하는 경우을 사용하십시오 intptr_t. 코드를 읽는 사람은 "aha, 이것은 아마도 바이트 단위의 크기"이고 "아, 어떤 이유로 포인터 값이 정수로 저장되어 있습니다"라는 것을 즉시 알게 될 것입니다.

그렇지 않으면 모든 것을 사용할 수 있습니다 unsigned long(또는 여기서는 현대에 unsigned long long). 크기는 모든 것이 아니며, 유형 이름은 프로그램을 설명하는 데 도움이되므로 유용합니다.


동의하지만 size_t필드에 포인터 유형을 저장하는 것과 관련된 해킹 / 트릭 (물론 분명히 문서화해야 함)을 고려하고 있었습니다.
Chris Lutz

@MarkAdler Standard에서는 포인터를 정수로 표현할 필요가 없습니다. 모든 포인터 유형을 정수 유형으로 변환 할 수 있습니다. 이전에 지정된 경우를 제외하고 결과는 구현 정의됩니다. 결과를 정수 유형으로 표시 할 수 없으면 동작이 정의되지 않은 것입니다. 결과는 정수 유형의 값 범위에있을 필요는 없습니다. 따라서, 단지 void*, intptr_tuintptr_t데이터에 대한 포인터를 대표 할 수 있도록 보장합니다.
Andrew Svietlichnyy 2016 년

12

가장 큰 배열의 크기가 포인터보다 작을 수 있습니다. 세그먼트 아키텍처를 생각해보십시오. 포인터는 32 비트 일 수 있지만 단일 세그먼트는 64KB (예 : 이전 리얼 모드 8086 아키텍처) 만 처리 할 수 ​​있습니다.

이들은 더 이상 데스크탑 컴퓨터에서 일반적으로 사용되지 않지만 C 표준은 소규모의 특수한 아키텍처도 지원하도록 고안되었습니다. 예를 들어 8 비트 또는 16 비트 CPU로 개발 된 임베디드 시스템이 여전히 있습니다.


그러나 배열처럼 포인터를 색인 할 수 있으므로 size_t처리 할 수 ​​있습니까? 아니면 일부 원거리 세그먼트의 동적 배열이 여전히 세그먼트 내에서 인덱싱으로 제한됩니까?
크리스 루츠

인덱싱 포인터는 기술적으로 가리키는 배열의 크기에 대해서만 지원되므로 배열이 64KB 크기로 제한되면 포인터 산술이 지원해야하는 전부입니다. 그러나 MS-DOS 컴파일러는 원거리 포인터 (32 비트 세그먼트 포인터)가 조작되어 전체 메모리를 단일 배열로 처리 할 수있는 '거대한'메모리 모델을 지원했지만, 뒤에서 포인터에 대한 대수는 다음과 같습니다. 꽤 추악한-오프셋이 16 (또는 다른 것)의 값을 넘어서 증가했을 때, 오프셋은 다시 0으로 랩되고 세그먼트 부분이 증가되었습니다.
Michael Burr

7
en.wikipedia.org/wiki/C_memory_model#Memory_segmentation을 읽고 우리가 자유로울 수 있도록 죽은 MS-DOS 프로그래머들에게 울어주십시오.
Justicle

더 나쁜 것은 stdlib 함수가 HUGE 키워드를 처리하지 않았다는 것입니다. 모든 16 비트 MS-C str심지어위한 기능 볼랜드 mem함수 ( memset, memcpy, memmove). 이것은 오프셋이 오버플로되었을 때 메모리의 일부를 덮어 쓸 수 있다는 것을 의미했습니다. 이는 임베디드 플랫폼에서 디버깅하기에 재미있었습니다.
Patrick Schlüter

@Justicle : 8086 세그먼트 아키텍처는 C에서 잘 지원되지 않지만 1MB 주소 공간이 충분하지만 64K 공간이 충분하지 않은 경우 더 효율적인 다른 아키텍처는 없습니다. 일부 최신 JVM은 실제로 32 비트 주소 공간에서 객체 기본 주소를 생성하기 위해 32 비트 객체 참조를 3 비트 왼쪽으로 이동하여 x86 실제 모드와 매우 유사한 주소 지정을 사용합니다.
supercat

5

코드에서 의도를 더 잘 전달한다고 생각합니다 (그리고 모든 유형 이름에 적용됩니다).

예를 들어, 비록 unsigned shortwchar_t사용, 윈도우 (내 생각)에 같은 크기 wchar_t대신 unsigned short쇼 당신이 아니라 그냥 임의의 수보다 넓은 문자를 저장하는 데 사용할 것이라는 의도를.


내 시스템에, - 그러나 여기에는 차이가있다 wchar_t보다 훨씬 더 큰 unsigned short사이의 이동성 문제 반면, 잘못하고 심각한 (현대) 이식성 문제를 만들 것, 다른 하나를 사용하여 그렇게 size_t하고이 uintptr_t땅 먼에 거짓말하는 것 의 1980- 뭔가 (날짜에 어둠 속에서 무작위로 찌르다)
크리스 루츠

터치! 그러나 다시, size_t그리고 uintptr_t그들의 이름에 여전히 암시 적으로 사용되었습니다.
dreamlax

그들은 그렇습니다. 나는 단순히 명확성 이상의 동기가 있는지 알고 싶었습니다. 그리고 거기에 있습니다.
Chris Lutz

3

앞뒤로보고, 다양한 홀수 볼 아키텍처가 풍경에 흩어져 있음을 상기하면서 기존의 모든 시스템을 래핑하고 미래의 모든 가능한 시스템을 제공하려고합니다.

확실히, 일이 해결되는 방식에는 지금까지 많은 유형이 필요하지 않았습니다.

그러나 다소 일반적인 패러다임 인 LP64에서도 시스템 호출 인터페이스에 size_t 및 ssize_t가 필요했습니다. 풀 64 비트 유형을 사용하는 것이 비싸고 4GB보다 큰 I / O 연산을 펀칭하고 64 비트 포인터가있는 더 제한적인 레거시 또는 미래 시스템을 상상할 수 있습니다.

나는 당신이 궁금해 할 것입니다 : 무엇이 개발되었을 지, 미래에 무엇이 올지. (아마 128 비트 분산 시스템 인터넷 전체 포인터이지만 시스템 호출에서 64 비트 이하, 또는 "레거시"32 비트 제한 일 수도 있습니다. :-) 레거시 시스템이 새로운 C 컴파일러를 얻을 수있는 이미지. .

또한 당시에 존재했던 것을 살펴보십시오. zillion 286 리얼 모드 메모리 모델 외에도 CDC 60 비트 워드 / 18 비트 포인터 메인 프레임은 어떻습니까? 크레이 시리즈는 어때? 정상적인 ILP64, LP64, LLP64는 신경 쓰지 마십시오. (저는 항상 마이크로 소프트가 LLP64에 집중한다고 생각했습니다. P64 여야했을 것입니다.) 확실히 모든 기반을 다루려는위원회를 상상할 수 있습니다 ...


-9
int main(){
  int a[4]={0,1,5,3};
  int a0 = a[0];
  int a1 = *(a+1);
  int a2 = *(2+a);
  int a3 = 3[a];
  return a2;
}

intptr_t가 항상 size_t를 대신해야하며 그 반대의 경우도 마찬가지입니다.


10
이 모든 것은 C의 특정 구문 문제입니다. 배열 인덱싱은 x [y]가 * (x + y)와 동일하다는 점에서 정의되며 a + 3과 3 + a는 유형과 값이 동일하기 때문에 3 [a] 또는 a [3]을 사용하십시오.
Fred Nurk
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.