차원, 색인 등의 경우 size_t 또는 int


15

C ++에서는 size_t(또는 더 정확하게 T::size_type"보통" size_t; 즉 unsigned유형 인)가에 대한 반환 값 size(),에 대한 인수 operator[]등으로 사용됩니다 (등 참조 std::vector).

반면에 .NET 언어 는 같은 목적 int으로 (그리고 선택적으로 long)를 사용합니다. 실제로 CLS 호환 언어는 서명되지 않은 형식을 지원할 필요없습니다 .

.NET이 C ++보다 최신이라는 점을 고려 하면 배열 인덱스 나 길이와 같이 "아마도"음수 일 수없는 경우에도 사용하는 데 문제 가있을 수 있습니다 unsigned int. 이전 버전과의 호환성을 위해 C ++ 접근 방식이 "역사 아티팩트"입니까? 아니면 두 접근 방식간에 실질적이고 중요한 설계 균형이 있습니까?

이것이 왜 중요한가? 글쎄 ... C ++에서 새로운 다차원 클래스에 무엇을 사용해야합니까? size_t또는 int?

struct Foo final // e.g., image, matrix, etc.
{
    typedef int32_t /* or int64_t*/ dimension_type; // *OR* always "size_t" ?
    typedef size_t size_type; // c.f., std::vector<>

    dimension_type bar_; // maybe rows, or x
    dimension_type baz_; // e.g., columns, or y

    size_type size() const { ... } // STL-like interface
};

6
주의 할 점 : .NET Framework의 여러 위치에서 -1"찾을 수 없음"또는 "범위를 벗어남"을 나타 내기 위해 인덱스를 반환하는 함수에서 반환됩니다. Compare()함수 (implementing IComparable) 에서도 반환됩니다 . 32 비트 int는 일반적인 숫자의 유형으로 간주됩니다. 바람직한 이유가 있기 때문입니다.
Robert Harvey

답변:


9

.NET이 C ++보다 최신이라는 것을 감안할 때, 배열 인덱스 나 길이와 같이 음수 일 가능성이없는 경우에도 부호없는 int를 사용하는 데 문제가있을 수 있습니다.

예. 이미지 처리 또는 배열 처리와 같은 특정 유형의 응용 프로그램의 경우 현재 위치와 관련된 요소에 액세스해야하는 경우가 종종 있습니다.

sum = data[k - 2] + data[k - 1] + data[k] + data[k + 1] + ...

이러한 유형의 응용 프로그램에서는 신중하게 생각하지 않고 부호없는 정수로 범위 검사를 수행 할 수 없습니다.

if (k - 2 < 0) {
    throw std::out_of_range("will never be thrown"); 
}

if (k < 2) {
    throw std::out_of_range("will be thrown"); 
}

if (k < 2uL) {
    throw std::out_of_range("will be thrown, without signedness ambiguity"); 
}

대신 범위 점검 표현식 을 재 배열 해야합니다. 이것이 주요 차이점입니다. 프로그래머는 정수 변환 규칙도 기억해야합니다. 확실하지 않은 경우 http://en.cppreference.com/w/cpp/language/operator_arithmetic#Conversions를 다시 읽으십시오 .

많은 응용 프로그램에서 매우 큰 배열 인덱스를 사용할 필요는 없지만 범위 검사를 수행해야합니다. 또한 많은 프로그래머들이이 표현 재배치 체조를하도록 훈련받지 않았습니다. 놓친 기회 하나가 악용의 문을 열어줍니다.

C #은 실제로 배열 당 2 ^ 31 개 이상의 요소가 필요하지 않은 응용 프로그램을 위해 설계되었습니다. 예를 들어 스프레드 시트 응용 프로그램은 많은 행, 열 또는 셀을 처리 할 필요가 없습니다. C #은 다음을 통해 상한을 처리합니다. 컴파일러 옵션을 망설이지 않고 키워드로 코드 블록에 대해 활성화 할 수있는 선택적인 산술 연산 을 을 처리합니다. 이러한 이유로 C #은 부호있는 정수를 선호합니다. 이러한 결정이 모두 고려 될 때, 이치에 맞습니다.

C ++은 단순히 다르며 올바른 코드를 얻기가 더 어렵습니다.

"최소 놀랍게도"의 잠재적 위반을 제거하기 위해 부호있는 산술을 허용하는 실질적인 중요성과 관련하여 매트릭스 요소 인덱스, 배열 크기, 픽셀 채널 수 등에 부호있는 32 비트 정수를 사용하는 OpenCV가 있습니다. 프로세싱은 상대 배열 인덱스를 많이 사용하는 프로그래밍 도메인의 예입니다. 부호없는 정수 언더 플로 (음수로 감싸 진 결과)는 알고리즘 구현을 심각하게 복잡하게합니다.


이것이 바로 내 상황입니다. 구체적인 예에 ​​감사드립니다. (예, 이것을 알고 있습니다 만 인용 할 "고등 당국"을 갖는 것이 유용 할 수 있습니다.)
Ðаn

1
@ Dan : 당신 무언가를 인용해야한다면, 이 게시물 이 더 좋을 것입니다.
rwong

1
@ Dan : John Regehr는 프로그래밍 언어로이 문제를 적극적으로 연구하고 있습니다. 참조 blog.regehr.org/archives/1401
rwong

반대 의견이 있습니다 : gustedt.wordpress.com/2013/07/15/…
rwong

14

이 답변은 진정으로 코드를 사용할 사람과보고 싶은 표준에 달려 있습니다.

size_t 목적을 가진 정수 크기입니다.

형식 size_t은 구현-정의 부호없는 정수 형식으로, 개체의 크기를 바이트 단위로 포함하기에 충분히 큽니다. (C ++ 11 사양 18.2.6)

따라서 객체 크기를 바이트 단위로 작업하려면 언제든지을 사용해야 size_t합니다. 많은 경우에, 이러한 차원 / 인덱스를 사용하여 바이트를 계산하지 않지만 대부분의 개발자 size_t는 일관성 을 위해이를 사용 합니다.

당신이해야합니다 항상 사용하는 size_t클래스가 STL 클래스의 모양과 느낌을 가질 것입니다 경우. 사양의 모든 STL 클래스가 사용 size_t됩니다. 컴파일러가 typedef를 typedef하는 것이 유효하고 typedef size_t를 to로 정의 하는 unsigned int것도 유효합니다 unsigned long. int또는 long직접 사용하는 경우 클래스를 STL 스타일을 따르는 것으로 생각하는 사람이 표준을 따르지 않아 갇히는 컴파일러가 결국 실행됩니다.

부호있는 유형을 사용하는 경우 몇 가지 장점이 있습니다.

  • 이름이 짧을 int수록 사람들이 입력하기가 쉽지만 코드를 복잡하게 만드는 것이 훨씬 어렵습니다 unsigned int.
  • 각 크기 당 하나의 정수-32 비트의 CLS 호환 정수는 Int32입니다. C ++에는 두 개의 ( int32_tuint32_t)가 있습니다. 이것은 API 상호 운용성을 더 간단하게 만들 수 있습니다

서명 된 유형의 큰 단점은 명백한 것입니다. 도메인의 절반을 잃게됩니다. 부호있는 숫자는 부호없는 숫자만큼 계산할 수 없습니다. C / C ++가 등장했을 때 이것은 매우 중요했습니다. 하나는 프로세서의 모든 기능을 처리 할 수 ​​있어야하고 서명되지 않은 숫자를 사용해야했습니다.

.NET을 대상으로하는 종류의 응용 프로그램에는 전체 도메인 서명되지 않은 인덱스가 필요하지 않았습니다. 이러한 숫자에 대한 많은 목적은 관리되는 언어에서는 유효하지 않습니다 (메모리 풀링이 떠 오릅니다). 또한 .NET이 출시되면서 64 비트 컴퓨터는 미래였습니다. 우리는 64 비트 정수의 전체 범위를 필요로하지 않기 때문에 1 비트를 희생하는 것이 이전만큼 고통스럽지 않습니다. 실제로 40 억 개의 인덱스가 필요한 경우 64 비트 정수 사용으로 전환하면됩니다. 최악의 경우 32 비트 시스템에서 실행하면 약간 느립니다.

나는 무역을 편의의 하나로 본다. 당신이 결코 사용하지 않을 약간의 색인 유형을 낭비하지 않을 정도로 충분한 컴퓨팅 성능을 가지고 있다면, 입력 int하거나 long멀리하는 것이 편리 합니다. 마지막 비트를 정말로 원한다면 숫자의 부호에주의를 기울 였을 것입니다.


의는의 구현 가정 해 봅시다 size()되었다 return bar_ * baz_;; 그것은 내가 사용하지 않으면 가질 수없는 정수 오버플로 (랩 어라운드)에 잠재적 인 문제를 일으키지 size_t않습니까?
Ðаn

5
@Dan 서명되지 않은 정수가 중요한 경우와 같은 경우를 구성 할 수 있으며,이 경우 전체 언어 기능을 사용하여 문제를 해결하는 것이 가장 좋습니다. 그러나 bar_ * baz_부호있는 정수는 오버플 로 할 수 있지만 부호없는 정수는 오버플 로 할 수 있는 클래스를 갖는 것이 흥미로운 구성이라고 말해야합니다 . C ++로 자신을 제한하면 부호없는 오버플로가 사양에 정의되어 있지만 부호있는 오버플로는 정의되지 않은 동작이므로 부호없는 정수의 모듈로 산술이 바람직하다면 실제로 정의되었으므로 반드시 사용하십시오!
Cort Ammon-복원 모니카

1
@Dan는 - 경우 (가) size()오버 플로우 서명 곱셈, 당신은 언어 UB 땅에있어. (그리고 fwrapv모드에서 다음을 참조하십시오.) 그렇다면 작은 비트를 조금 더 사용하면 부호없는 곱셈 이 오버플로 되어 사용자 코드 버그 랜드에 있습니다. 거짓 크기를 반환합니다. 따라서 서명되지 않은 구매는 여기에서 많이 생각하지 않습니다.
Martin Ba

4

위의 rwong대답은 이미 문제를 잘 강조 한다고 생각합니다 .

002를 추가하겠습니다 :

  • size_t즉, 그 크기는 ...

    이론적으로 가능한 모든 유형 (배열 포함)의 최대 크기를 저장할 수 있습니다.

    ...은 sizeof(type)==1바이트 ( char) 유형을 처리하는 경우 범위 인덱스에만 필요합니다 . (그러나 우리 는 ptr 유형보다 작을 수 있습니다 .

  • 따라서 xxx::size_type99.9 %의 경우 부호있는 크기의 경우에도 사용할 수 있습니다. (비교 ssize_t)
  • 사실 std::vector선택과 친구 size_t, 부호 크기와 인덱싱, 형식이됩니다 몇 가지 고려 설계 결함이있을 수 있습니다. 동의합니다. (5 분 동안 CppCon 2016 : Jon Kalb의“서명되지 않은 코드 : 더 나은 코드를위한 지침”)을 시청하십시오 .
  • 오늘날 C ++ API를 설계 할 때는 다음과 같이 빡빡 size_t해야합니다. 표준 라이브러리와 일치하도록 사용하거나 ( 부호있는 ) intptr_t또는ssize_t 간단하고 적은 버그가 발생하기 쉬운 인덱스 계산.
  • int32 또는 int64를 사용하지 마십시오. intptr_t서명하고 기계어 크기를 원 하거나을 사용하려면 사용하십시오 ssize_t.

( "인덱싱"또는) 주소 공간의 절반 이상을 처리해야하는 이론적 문제는 aehm과 같은 저수준 언어로 처리 되어야하기 때문에이 질문에 직접 대답하기 위해서는 "역사적 인공물"이 아닙니다. C ++.

지나고 나서 보니, 내가 개인적으로 생각, 그것은 이다 표준 라이브러리가 서명 사용하는 설계 결함 size_t모두가 원시 메모리 크기를 표시하지 않는 경우에도 여기 저기 있지만, 모음 등의 입력 데이터의 용량은 :

  • 주어진 C ++의 정수 승격 규칙 ->
  • 부호없는 유형은 의미 적으로 부호가없는 크기와 같은 "의미 적"유형에 대한 훌륭한 후보를 만들지 않습니다.

나는 Jon의 충고를 여기 에서 반복 할 것이다 :

  • 지원하는 작업 유형을 선택하십시오 (값 범위가 아님). (*1)
  • API에서 서명되지 않은 유형을 사용하지 마십시오. 이로 인해 거꾸로되는 이점없이 버그가 숨겨집니다.
  • 수량에 "부호없는"을 사용하지 마십시오. (* 2)

(* 1) 즉, unsigned == 비트 마스크, 절대로 수학을 수행하지 마십시오 (여기서는 첫 번째 예외가 발생합니다. 랩하는 카운터가 필요할 수 있습니다. 이것은 부호없는 유형이어야합니다.)

(* 2) 수량은 계산하거나 계산하는 것을 의미합니다.


"완전히 사용 가능한 플랫 메모리"란 무엇을 의미합니까? 또한, 대신에 ssize_t서명 된 펜던트로 정의 되지 않은 (멤버가 아닌) 포인터를 저장할 수 있으므로 더 클 수 있습니다. size_tintptr_t
중복 제거기

@ 중복 제거기-글쎄, 나는 size_t약간 엉망인 정의를 얻었을 것 같다 . size_t 대 intptren.cppreference.com/w/cpp/types/size_t를 참조하십시오 . 오늘 새로운 것을 배웠습니다. :-) 나머지 인수는 그대로 있다고 생각합니다. 사용 된 유형을 수정할 수 있는지 볼 수 있습니다.
Martin Ba

0

성능상의 이유로 보통 size_t를 사용하여 추가 합니다. 하여 잘못 계산하면 언더 플로가 발생하여 범위 검사 (0 이하 및 size () 이상)가 1로 감소 할 수 있음을 추가합니다.

부호있는 int 사용 :

int32_t i = GetRandomNumberFromRange(-1000, 1000);

if (i < 0)
{
    //error
}

if (i > size())
{
    //error
}

부호없는 int 사용 :

int32_t i = GetRandomNumberFromRange(-1000, 1000);

/// This will underflow any number below zero, so that it becomes a very big *positive* number instead.
uint32_t asUnsigned = static_cast<uint32_t>(i);

/// We now don't need to check for below zero, since an unsigned integer can only be positive.
if (asUnsigned > size())
{
    //error
}

1
당신은 정말로 하나 더 철저하게 설명하고 싶습니다.
Martin Ba

대답을 더 유용하게 만들려면 정수 배열 범위 또는 오프셋 비교 (부호 및 부호없는)가 다양한 컴파일러 공급 업체의 기계 코드에서 어떻게 보이는지 설명 할 수 있습니다. 지정된 C ++ 코드 및 컴파일러 플래그에 해당하는 컴파일 된 기계 코드를 표시 할 수있는 많은 온라인 C ++ 컴파일러 및 디스 어셈블리 사이트가 있습니다.
rwong

나는 이것을 좀 더 설명하려고 노력했다.
asger
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.