your programing

size_t가 서명되지 않은 이유는 무엇입니까?

lovepro 2020. 12. 30. 19:52
반응형

size_t가 서명되지 않은 이유는 무엇입니까?


Bjarne Stroustrup은 C ++ 프로그래밍 언어에서 다음과 같이 썼습니다.

부호없는 정수 유형은 스토리지를 비트 배열로 취급하는 용도에 이상적입니다. int 대신 unsigned를 사용하여 양의 정수를 나타 내기 위해 1 비트를 더 얻는 것은 거의 좋은 생각이 아닙니다. 부호없는 변수를 선언하여 일부 값이 양수인지 확인하려는 시도는 일반적으로 암시 적 변환 규칙에 의해 무효화됩니다.

size_t 는 "양의 정수를 나타 내기 위해 1 비트를 더 얻기 위해"부호가없는 것처럼 보입니다. 그렇다면 이것은 실수 (또는 절충)였으며, 그렇다면 우리 코드에서 사용을 최소화해야합니까?

Scott Meyers의 또 다른 관련 기사는 여기에 있습니다 . 요약하면 값이 항상 양수인지 여부에 관계없이 서명되지 않은 인터페이스를 사용하지 않는 것이 좋습니다. 즉, 음수 값이 의미가 없더라도 반드시 unsigned를 사용해야하는 것은 아닙니다.


size_t 역사적인 이유로 서명되지 않았습니다.

"소형"모델 DOS 프로그래밍과 같이 16 비트 포인터가있는 아키텍처에서는 문자열을 32KB로 제한하는 것이 비현실적입니다.

이러한 이유로 C 표준은 (필요한 범위를 통해) ptrdiff_t, 부호있는 대응 요소 size_t및 포인터 차이의 결과 유형이 사실상 17 비트 여야합니다.

이러한 이유는 임베디드 프로그래밍 세계의 일부에 여전히 적용될 수 있습니다.

그러나 C 및 C ++의 불행한 암시 적 변환 규칙이 부호없는 유형이 숫자에 사용될 때 버그 유인 자로 만든다는 점에서 훨씬 더 중요한 고려 사항이있는 최신 32 비트 또는 64 비트 프로그래밍에는 적용되지 않습니다. 따라서 산술 연산 및 크기 비교). 20 ~ 20 개의 뒤를 돌아 보면 예를 들어 string( "Hi" ).length() < -3실질적으로 보장 되는 특정 변환 규칙을 채택하기로 한 결정 이 다소 어리 석고 비실용적이라는 것을 알 수 있습니다. 그러나이 결정은 현대 프로그래밍에서 숫자에 부호없는 유형을 채택하는 것은 심각한 단점과 장점이 없음을 의미합니다. 단, unsigned자기 설명 유형 이름으로 인식되고를 생각하지 못하는 사람들의 감정을 만족시키는 것 외에는 예외 입니다 typedef int MyType.

요약하면 실수가 아닙니다. 당시 매우 합리적이고 실용적인 프로그래밍 이유 때문이었습니다. 파스칼과 같은 경계 검사 언어에서 C ++로 기대치를 전달하는 것과는 아무런 관련이 없습니다 (오류이지만 파스칼에 대해 들어 본 적이없는 경우에도 매우 일반적인 것입니다).


size_tunsigned음수 크기는 의미 없기 때문입니다.

(댓글에서 :)

그것이 무엇인지 말하는 것만 큼 확실하지 않습니다. -1 크기 목록을 마지막으로 본 게 언제입니까? 그 논리를 너무 많이 따르면 unsigned가 전혀 존재하지 않아야하고 비트 연산도 허용되지 않아야한다는 것을 알게됩니다. 괴짜

요점 : 생각해야하는 이유로 주소에는 서명이 없습니다. 크기는 주소를 비교하여 생성됩니다. 주소를 서명 된 것으로 취급하면 결과에 대해 서명 된 값을 사용하면 Stroustrup 인용문을 읽는 것이 허용 가능하다고 생각하는 방식으로 데이터가 손실되지만 실제로는 그렇지 않습니다. 대신 부정적인 주소가 무엇을해야하는지 설명 할 수 있습니다. 괴짜


인덱스 유형을 unsigned로 만드는 이유는 반 개방 간격에 대한 C 및 C ++의 기본 설정과의 대칭 때문입니다. 그리고 인덱스 유형이 서명되지 않은 경우 크기 유형도 서명되지 않은 것이 편리합니다.


C에서는 배열을 가리키는 포인터를 가질 수 있습니다. 유효한 포인터는 배열의 모든 요소 또는 배열의 끝을 지나는 한 요소를 가리킬 수 있습니다. 배열 시작 앞의 한 요소를 가리킬 수 없습니다.

int a[2] = { 0, 1 };
int * p = a;  // OK
++p;  // OK, points to the second element
++p;  // Still OK, but you cannot dereference this one.
++p;  // Nope, now you've gone too far.
p = a;
--p;  // oops!  not allowed

C ++는이 아이디어에 동의하고 반복자로 확장합니다.

부호없는 인덱스 유형에 대한 인수는 종종 배열을 뒤에서 앞으로 순회하는 예를 보여 주며 코드는 종종 다음과 같습니다.

// WARNING:  Possibly dangerous code.
int a[size] = ...;
for (index_type i = size - 1; i >= 0; --i) { ... }

이 코드는 인덱스 유형이 서명되어야한다는 인수로 사용되는가 서명 된 경우 에만 작동 합니다index_type (확장하면 크기도 서명되어야 함).

그 코드는 비 관용적이기 때문에 그 주장은 설득력이 없습니다. 인덱스 대신 포인터로이 루프를 다시 작성하려고하면 어떤 일이 발생하는지 살펴보십시오.

// WARNING:  Bad code.
int a[size] = ...;
for (int * p = a + size - 1; p >= a; --p) { ... }

Yikes, 이제 정의되지 않은 동작이 있습니다! size0 일 때 문제를 무시하면 첫 번째 요소 이전의 요소를 가리키는 잘못된 포인터를 생성하기 때문에 반복 끝에 문제가 있습니다. 포인터를 역 참조하지 않더라도 정의되지 않은 동작입니다.

따라서 첫 번째 요소 이전에 요소를 가리키는 포인터를 갖는 것이 합법적으로되도록 언어 표준을 변경하여이 문제를 해결할 수 있지만 그럴 가능성은 없습니다. 반 개방 간격은 이러한 언어의 기본 구성 요소이므로 대신 더 나은 코드를 작성해 보겠습니다.

올바른 포인터 기반 솔루션은 다음과 같습니다.

int a[size] = ...;
for (int * p = a + size; p != a; ) {
  --p;
  ...
}

많은 사람들은 감소가 이제 헤더가 아닌 루프 본문에 있기 때문에 이것을 방해한다고 생각하지만 for 구문이 주로 반 개방 간격을 통한 순방향 루프를 위해 설계되었을 때 발생합니다. (역 반복기는 감소를 연기하여이 비대칭을 해결합니다.)

이제 비유하면 인덱스 기반 솔루션은 다음과 같습니다.

int a[size] = ...;
for (index_type i = size; i != 0; ) {
  --i;
  ...
}

이것은 index_type서명 여부에 관계없이 작동 하지만 서명되지 않은 선택은 관용적 포인터 및 반복기 버전에 더 직접 매핑되는 코드를 생성합니다. Unsigned는 포인터와 반복자와 마찬가지로 시퀀스의 모든 요소에 액세스 할 수 있음을 의미합니다. 무의미한 값을 나타 내기 위해 가능한 범위의 절반을 포기하지 않습니다. 이는 64 비트 세계에서 실질적인 문제는 아니지만 16 비트 임베디드 프로세서에서 또는 여전히 동일한 API를 제공 할 수있는 방대한 범위의 희소 데이터에 대한 추상 컨테이너 유형을 구축하는 데있어 매우 중요한 문제가 될 수 있습니다. 네이티브 컨테이너.


반면에 ...

오해 1 : std::size_t서명되지 않은 것은 더 이상 적용되지 않는 레거시 제한 때문입니다.

여기에서 일반적으로 언급되는 두 가지 "역사적"이유가 있습니다.

  1. sizeof returns std::size_t, which has been unsigned since the days of C.
  2. Processors had smaller word sizes, so it was important to squeeze that extra bit of range out.

But neither of these reasons, despite being very old, are actually relegated to history.

sizeof still returns a std::size_t which is still unsigned. If you want to interoperate with sizeof or the standard library containers, you're going to have to use std::size_t.

The alternatives are all worse: You could disable signed/unsigned comparison warnings and size conversion warnings and hope that the values will always be in the overlapping ranges so that you can ignore the latent bugs using different types couple potentially introduce. Or you could do a lot of range-checking and explicit conversions. Or you could introduce your own size type with clever built-in conversions to centralize the range checking, but no other library is going to use your size type.

And while most mainstream computing is done on 32- and 64-bit processors, C++ is still used on 16-bit microprocessors in embedded systems, even today. On those microprocessors, it's often very useful to have a word-sized value that can represent any value in your memory space.

Our new code still has to interoperate with the standard library. If our new code used signed types while the standard library continues to use unsigned ones, we make it harder for every consumer that has to use both.

Myth 2: You don't need that extra bit. (A.K.A., You're never going to have a string larger than 2GB when your address space is only 4GB.)

Sizes and indexes aren't just for memory. Your address space may be limited, but you might process files that are much larger than your address space. And while you might not have a string with more the 2GB, you could comfortably have a bitset with more than 2Gbits. And don't forget virtual containers designed for sparse data.

Myth 3: You can always use a wider signed type.

Not always. It's true that for a local variable or two, you could use a std::int64_t (assuming your system has one) or a signed long long and probably write perfectly reasonable code. (But you're still going to need some explicit casts and twice as much bounds checking or you'll have to disable some compiler warnings that might've alerted you to bugs elsewhere in your code.)

But what if you're building a large table of indices? Do you really want an extra two or four bytes for every index when you need just one bit? Even if you have plenty of memory and a modern processor, making that table twice as large could have deleterious effects on locality of reference, and all your range checks are now two-steps, reducing the effectiveness of branch prediction. And what if you don't have all that memory?

Myth 4: Unsigned arithmetic is surprising and unnatural.

This implies that signed arithmetic is not surprising or somehow more natural. And, perhaps it is when thinking in terms of mathematics where all the basic arithmetic operations are closed over the set of all integers.

But our computers don't work with integers. They work with an infinitesimal fraction of the integers. Our signed arithmetic is not closed over the set of all integers. We have overflow and underflow. To many, that's so surprising and unnatural, they mostly just ignore it.

This is bug:

auto mid = (min + max) / 2;  // BUGGY

If min and max are signed, the sum could overflow, and that yields undefined behavior. Most of us routinely miss this these kinds of bugs because we forget that addition is not closed over the set of signed ints. We get away with it because our compilers typically generate code that does something reasonable (but still surprising).

If min and max are unsigned, the sum could still overflow, but the undefined behavior is gone. You'll still get the wrong answer, so it's still surprising, but not any more surprising than it was with signed ints.

The real unsigned surprise comes with subtraction: If you subtract a larger unsigned int from a smaller one, you're going to end up with a big number. This result isn't any more surprising than if you divided by 0.

Even if you could eliminate unsigned types from all your APIs, you still have to be prepared for these unsigned "surprises" if you deal with the standard containers or file formats or wire protocols. Is it really worth adding friction to your APIs to "solve" only part of the problem?

ReferenceURL : https://stackoverflow.com/questions/10168079/why-is-size-t-unsigned

반응형