프로그램 - reference c++




왜 유형은 그 가치에 관계없이 항상 특정 크기입니까? (13)

왜 myInt는 단지 1 바이트의 메모리를 차지하지 않을까요?

그걸 많이 사용하라고 했으니 까. 를 사용할 때 unsigned int , 일부 표준은 4 바이트가 사용될 것이며 사용 가능한 범위는 0에서 4,294,967,295가 될 것이라고 지시합니다. unsigned char 대신 당신이 사용하는 경우, 당신은 아마 당신이 찾고있는 1 바이트를 사용하는 것입니다 (표준에 따라 C + +를 일반적으로 이러한 표준을 사용).

이러한 표준을 따르지 않았다면 이것을 염두에 두어야합니다. 컴파일러 나 CPU가 4 대신 1 바이트 만 사용한다는 것을 어떻게 알아야합니까? 나중에 프로그램에서 더 많은 공간을 필요로하는 값을 추가하거나 곱할 수 있습니다. 메모리를 할당 할 때마다 OS는 해당 공간을 찾아서 매핑하고 제공해야합니다 (잠재적으로 가상 RAM에도 메모리를 스왑 할 수 있음). 이것은 오랜 시간이 걸릴 수 있습니다. 메모리를 미리 할당하면 다른 할당이 완료 될 때까지 기다릴 필요가 없습니다.

바이트 당 8 비트를 사용하는 이유는 다음과 같습니다. 바이트가 8 비트 인 이유는 무엇입니까?

보조 노트에서 정수가 오버플로되도록 허용 할 수 있습니다. 그러나 부호가있는 정수를 사용해야하는 경우 C \ C ++ 표준은 정수 오버플로가 정의되지 않은 동작을 초래한다는 것을 나타냅니다. 정수 오버플로

구현은 실제 유형의 유형간에 다를 수 있지만 대부분 unsigned int 및 float와 같은 유형은 항상 4 바이트입니다. 그런데 유형이 항상 그 가치에 관계없이 일정량의 메모리를 차지하는 이유는 무엇입니까? 예를 들어, 255의 값으로 다음 정수를 만든 경우

int myInt = 255;

그렇다면 myInt 는 내 컴파일러에서 4 바이트를 차지합니다. 그러나 실제 값인 255 는 1 바이트로만 표시 될 수 있습니다. 따라서 myInt 가 메모리의 1 바이트를 차지하는 이유는 무엇입니까? 또는 좀 더 일반적인 질문 방법 : 왜 값을 표현하는 데 필요한 공간이 그 크기보다 작을 때 형식에 연관된 크기가 하나뿐입니까?


왜 값을 표현하는 데 필요한 공간이 그 크기보다 작을 때 형식에 연관된 크기가 하나뿐입니까?

주로 정렬 요구 사항 때문입니다.

basic.align/1 :

객체 유형은 해당 유형의 객체가 할당 될 수있는 주소에 제한을 두는 정렬 요구 사항을가집니다.

많은 층이 있고 각 층에는 많은 방이있는 건물을 생각해보십시오.
각 방은 N 개의 사람이나 물건을 수용 할 수있는 크기 (고정 된 공간)입니다.
사전에 알려진 공간 크기로 건물의 구조적 구성 요소를 체계적으로 구성 합니다.

방이 정렬되지 않으면 건물 뼈대가 잘 구조화되지 않습니다.


그것은 더 적을 수 있습니다. 다음 함수를 고려하십시오.

int foo()
{
    int bar = 1;
    int baz = 42;
    return bar+baz;
}

어셈블리 코드로 컴파일됩니다 (g ++, x64, 세부 사항은 제거되었습니다)

$43, %eax
ret

여기, bar 그리고 baz 0 바이트를 사용하여 끝낼 수 있습니다.


대부분의 대답을 놓치지 않는 간단한 것 :

C ++의 설계 목표에 부합하기 때문입니다.

컴파일 타임에 타입의 크기를 계산할 수 있다면, 컴파일러와 프로그래머가 가정을 간소화 할 수 있으며, 특히 성능 측면에서 많은 이점을 가져다줍니다. 물론 고정 크기 유형에는 정수 넘침과 같은 함정이 있습니다. 이것이 서로 다른 언어가 서로 다른 디자인 결정을 내리는 이유입니다. (예를 들어, 파이썬 정수는 기본적으로 가변 크기입니다.)

아마도 C ++이 고정 크기 타입에 강하게 의존하는 주된 이유는 C 호환성이라는 목표 때문일 것입니다. 그러나 C ++은 정적 인 형식의 언어이므로 매우 효율적인 코드를 생성하고 프로그래머가 명시 적으로 지정하지 않은 것을 추가하지 않으므로 고정 크기 유형은 여전히 ​​많은 의미를 갖습니다.

그렇다면 왜 C가 고정 크기 유형을 선택 했습니까? 단순한. '70 년대 시대의 운영체제, 서버 소프트웨어 및 유틸리티를 작성하기 위해 고안되었습니다. 다른 소프트웨어를위한 인프라 (예 : 메모리 관리)를 제공하는 것들. 그렇게 낮은 수준에서 성능은 중요합니다. 컴파일러는 여러분이 말하는 것과 정확하게 일치합니다.


변수의 크기를 변경하려면 재 할당이 필요하며 보통 메모리를 몇 바이트 더 낭비하는 것과 비교하여 CPU주기를 추가 할 가치가 없습니다.

지역 변수는 그 변수가 크기가 변하지 않을 때 매우 빠르게 조작 할 수있는 스택으로 이동합니다. 변수의 크기를 1 바이트에서 2 바이트로 확장하기로 결정했다면, 그 공간을 만들기 위해 스택의 모든 것을 1 바이트 씩 이동해야합니다. 따라서 이동해야하는 항목 수에 따라 CPU주기가 많이 소요될 수 있습니다.

여러분이 할 수있는 또 다른 방법은 모든 변수를 힙 위치에 대한 포인터로 만드는 것이지만 실제로는 CPU 사이클과 메모리를 이렇게 많이 낭비 할 것입니다. 포인터는 4 바이트 (32 비트 주소 지정) 또는 8 바이트 (64 비트 주소 지정)이므로, 포인터에 대해 4 또는 8을 사용하고, 힙에있는 데이터의 실제 크기를 사용하고 있습니다. 이 경우 재 할당 비용은 여전히 ​​있습니다. 힙 데이터를 재 할당해야 할 경우 운이 좋게도 인라인으로 확장 할 여지가 있지만 힙의 다른 위치로 이동해야 원하는 크기의 연속적인 메모리 블록을 가질 수 있습니다.

언제나 얼마나 많은 메모리를 사용할지 결정하는 것이 더 빠릅니다. 동적 크기 조정을 피할 수 있으면 성능이 향상됩니다. 메모리 낭비는 대개 성능 향상에 가치가 있습니다. 이것이 바로 컴퓨터에 많은 메모리가있는 이유입니다. :)


어떤 의미에서는 C ++ 표준 라이브러리에서 가변 크기를 갖는 객체가 있습니다 std::vector . 그러나 이들은 모두 동적으로 필요한 여분의 메모리를 할당합니다. 가져 가면 sizeof(std::vector<int>) 객체가 관리하는 메모리와 아무런 관련이없는 상수를 얻을 수 있습니다. 포함하는 배열이나 구조체를 할당 std::vector<int> 하면 동일한 배열이나 구조에 여분의 저장 공간을두기보다는이 기본 크기를 예약합니다 . 이런 식으로, 특히 가변 길이 배열과 구조를 지원하는 몇 가지 C 구문이 있지만 C ++에서는이를 지원하지 않습니다.

언어 표준은 컴파일러가 효율적인 코드를 생성 할 수 있도록 객체 크기를 정의합니다. 예를 들어, int 일부 구현에서 4 바이트 길이가 있고 값에 a 대한 포인터 또는 int 값의 배열로 선언 a[i] 하면 의사 코드 로 변환됩니다. "주소 a + 4 × i를 참조 해제하십시오."이것은 일정 시간 내에 수행 될 수 있습니다 x86과 C가 원래 개발 된 DEC PDP 시스템을 포함한 많은 명령어 세트 아키텍처가 단일 기계 명령어로 수행 할 수있는 공통적이고 중요한 작업입니다.

가변 길이 단위로 연속적으로 저장된 데이터의 실제적인 예는 UTF-8로 인코딩 된 문자열입니다. (단, 컴파일러에 UTF-8 문자열의 기본 유형은 아직도 char 하고있다 폭 1.이 ASCII 문자열이 유효한 UTF-8 및 라이브러리 코드 등의 많은으로 해석 할 수 있도록 strlen() 하고 strncpy() 작업을 계속합니다.) 모든 UTF-8 코드 포인트의 인코딩은 1에서 4 바이트 길이가 될 수 있으므로 문자열에 다섯 번째 UTF-8 코드 포인트가 필요하면 데이터의 다섯 번째 바이트에서 열 여섯 번째 바이트까지 어디서나 시작할 수 있습니다. 문자열의 처음부터 스캔하여 각 코드 포인트의 크기를 확인하는 유일한 방법입니다. 다섯 번째 자필 을 찾고 싶다면 , 문자 클래스도 확인해야합니다. 문자열에서 백만 번째 UTF-8 문자를 찾으려면이 루프를 백만 번 실행해야합니다! 인덱스를 자주 사용해야하는 경우 문자열을 한 번 탐색하여 인덱스를 만들거나 UCS-4와 같은 고정 너비 인코딩으로 변환 할 수 있습니다. 문자열에서 백만 번째 UCS-4 문자를 찾는 것은 배열의 주소에 4 백만을 추가하는 것입니다.

가변 길이 데이터의 또 다른 복잡성은 할당 할 때 사용 가능한만큼의 메모리를 할당하거나 필요한 경우 동적으로 재 할당해야한다는 것입니다. 최악의 경우에 할당하는 것은 매우 낭비 일 수 있습니다. 연속적인 메모리 블록이 필요한 경우 재 할당하면 모든 데이터를 다른 위치로 복사해야하지만 메모리를 비 연속 청크에 저장하면 프로그램 논리가 복잡해집니다.

그래서, 가변 길이 bignums 대신 고정 폭을 가질 수있어 short int , int , long intlong long int , 그러나 할당하고 사용하는 것이 비효율적이다. 또한 모든 주류 CPU는 고정 너비 레지스터에서 산술 연산을 수행하도록 설계되어 있으며 일종의 가변 길이 bignum에서 직접 연산하는 명령어는 없습니다. 그것들은 훨씬 느리게 소프트웨어로 구현 될 필요가 있습니다.

실제 세계에서, 대부분의 프로그래머는 UTF-8 인코딩의 이점, 특히 호환성을 중요시하며, 문자열을 앞뒤로 스캔하거나 블록을 복사하는 것 외에는 거의 신경 쓰지 않는다고 결정했습니다. 가변 폭의 단점을 수용 할 수있는 메모리 다른 것들을 위해 UTF-8과 비슷한 가변 길이의 가변 길이 요소를 사용할 수 있습니다. 그러나 우리는 거의하지 않으며 표준 라이브러리에 없습니다.


C ++과 같은 언어에서, 설계 목표는 간단한 연산이 간단한 기계 명령어로 컴파일된다는 것입니다.

모든 주류 CPU 명령어 세트는 고정 너비 유형과 함께 작동하며, 가변 너비 유형을 수행하려는 경우이를 처리하기 위해 여러 가지 기계 명령어를 수행해야합니다.

기본 컴퓨터 하드웨어가 그런 식으로되어 있는지에 관해서는 : 그것은 모든 경우가 아니라 더 많은 경우에 더 간단하고 더 효율적이기 때문입니다.

컴퓨터를 테이프 조각으로 상상해보십시오.

| xx | xx | xx | xx | xx | xx | xx | xx | xx | xx | xx | xx | xx | ...

컴퓨터가 테이프의 첫 번째 바이트 ( xx 를 보도록 컴퓨터에 알리면 유형이 거기에서 멈추거나 다음 바이트로 진행되는지 여부를 어떻게 알 수 있습니까? 255 (16 진수 FF ) 또는 65535 (16 진수 FFFF )와 같은 숫자가있는 경우 첫 번째 바이트는 항상 FF 입니다.

그럼 어떻게 압니까? 추가 논리를 추가하고 적어도 하나의 비트 또는 바이트 값의 의미를 "오버로드"하여 값이 다음 바이트로 계속됨을 나타냅니다. 그 논리는 결코 "자유"가 아니며, 소프트웨어로 에뮬레이션하거나 CPU에 추가 트랜지스터를 추가하여 수행 할 수 있습니다.

C 및 C ++와 같은 고정 너비 유형의 언어는이를 반영합니다.

이 방식 일 필요 는 없으며 최대한 효율적으로 코드에 매핑하는 것에 덜 관심있는 추상 언어는 숫자 유형에 가변 길이 인코딩 (가변 길이 수량 또는 VLQ라고도 함)을 자유롭게 사용할 수 있습니다.

추가 읽기 : "가변 길이 수량"을 검색하면 해당 종류의 인코딩 실제로 효율적이고 추가 로직의 가치가있는 예제를 찾을 수 있습니다. 일반적으로 큰 범위의 값을 저장할 필요가있을 때 대부분의 값은 작은 하위 범위로 이동합니다.

컴파일러가 코드를 손상시키지 않고 (예를 들어 변수가 단일 번역 단위 내에서만 내부적으로 볼 수 있음) 더 작은 공간에 값을 저장하는 것으로 벗어날 수 있다는 것을 증명할 수 있다면 최적화 추론은 ' 대상 하드웨어에서 더 효율적 일 것입니다. 코드의 나머지 부분이 표준 작업을 수행 한 것처럼 "적절 하게 최적화하여 적은 공간에 저장할 수 있습니다.

그러나 코드가 별도로 컴파일 될 수있는 다른 코드와 상호 작동 해야하는 경우 크기가 일관성을 유지하거나 모든 코드가 동일한 규칙을 따르도록해야합니다.

일관성이 없으면 다음과 같은 복잡성이 있습니다. int x = 255; 경우 어떻게됩니까? 하지만 나중에 코드에서 x = y 할까요? int 가 가변 폭 일 수있는 경우, 컴파일러는 필요한 최대 공간을 사전에 미리 할당해야합니다. 그것은 항상 가능하지 않습니다. 왜냐하면 y 가 별도로 컴파일 된 다른 코드에서 전달되는 인수 일 때 무엇이겠습니까?


Java는 "BigInteger"및 "BigDecimal"이라는 클래스를 사용하여 C ++의 GMP C ++ 클래스 인터페이스와 마찬가지로이 작업을 수행합니다 (Digital Trauma에게 감사드립니다). 원한다면 거의 모든 언어로 쉽게 할 수 있습니다.

CPU는 항상 모든 길이의 연산을 지원하도록 설계된 BCD (Binary Coded Decimal)을 사용할 수 있습니다 (하지만 현재 GPU 표준에 따라 SLOW 일 때 한 번에 한 바이트 씩 수동으로 조작하는 경향이 있습니다).

우리가 이러한 또는 다른 유사한 해결책을 사용하지 않는 이유는 무엇입니까? 공연. 가장 뛰어난 언어는 긴밀한 루프 연산의 중간에 변수를 확장 할 여력이 없습니다. 이는 매우 비 결정적입니다.

대용량 저장 및 전송 상황에서 압축 된 값은 흔히 사용하는 유일한 값 유형입니다. 예를 들어 컴퓨터로 스트리밍되는 음악 / 비디오 패킷은 다음 값이 크기 최적화로 2 바이트인지 또는 4 바이트인지를 지정하기 위해 약간을 소비 할 수 있습니다.

일단 컴퓨터에서 사용할 수 있지만 메모리는 저렴하지만 크기를 조정할 수있는 속도와 복잡성은 실제로는 유일한 이유입니다.


동적 크기의 간단한 유형을 사용하는 것은 매우 복잡하고 계산이 많기 때문입니다. 이것이 가능할 지 확신하지 못합니다.
컴퓨터는 값이 바뀔 때마다 숫자가 몇 비트인지 확인해야합니다. 추가 작업이 많이 필요할 것입니다. 편집하는 동안 변수의 크기를 모를 때 계산을 수행하는 것이 훨씬 더 어려울 것입니다.

변수의 동적 크기를 지원하기 위해 컴퓨터는 변수가 현재 얼마나 많은 바이트를 가지고 있는지 기억해야합니다. 그 정보를 저장하기 위해 추가 메모리가 필요할 것입니다. 그리고이 정보는 변수에 대한 모든 연산 전에 올바른 프로세서 명령어를 선택하기 전에 분석되어야합니다.

컴퓨터가 작동하는 방식과 변수의 크기가 일정한 이유를 더 잘 이해하려면 어셈블러 언어의 기본 사항을 익히십시오.

하지만, constexpr 값을 사용하면 이와 비슷한 것을 얻을 수 있다고 생각합니다. 그러나 이렇게하면 프로그래머가 코드를 예측하기가 어려워집니다. 나는 몇몇 컴파일러 최적화가 그런 것을 할 수 있다고 생각하지만, 프로그래머가 그것을 단순하게 유지하기 위해 그것을 숨긴다.

여기서 프로그램의 성능과 관련된 문제 만 설명했습니다. 변수의 크기를 줄임으로써 메모리를 절약하기 위해 해결해야 할 모든 문제는 생략했습니다. 솔직히, 나는 그것이 가능하다고 생각하지 않는다.

결론적으로, 선언 된 것보다 작은 변수를 사용하면 편집하는 동안 값이 알려지지 않은 경우에만 의미가 있습니다. 현대 컴파일러가 그렇게 할 가능성은 매우 높습니다. 다른 경우에는 너무 많은 하드 또는 심지어 해결할 수없는 문제를 일으킬 것입니다.


유형은 기본적으로 저장소를 나타내며 현재 유형이 아닌 보유 할 수있는 최대 값으로 정의됩니다.

아주 간단한 유추는 집이 될 것입니다. 집은 얼마나 많은 사람들이 살고 있든 상관없이 고정 된 크기를 가지고 있으며, 특정 크기의 집에 살 수있는 최대 인원을 규정하는 건물 코드도 있습니다.

그러나 한 사람이 10 명을 수용 할 수있는 집에 살고 있다고해도 집의 크기는 현재 거주자 수에 영향을받지 않습니다.


짧은 대답은 : C ++ 표준에서 그렇게 말하고 있기 때문입니다.

긴 대답은 다음과 같습니다. 컴퓨터로 수행 할 수있는 작업은 궁극적으로 하드웨어에 의해 제한됩니다. 물론, 정수를 다양한 바이트 수로 인코딩하여 저장하는 것이 가능하지만,이를 읽으려면 특수한 CPU 명령어가 필요합니다. 그렇지 않으면 소프트웨어로 구현할 수 있지만 속도가 매우 느립니다. 미리 정의 된 너비의 값을로드하기 위해 고정 크기 작업을 CPU에서 사용할 수 있으며 가변 너비에는 아무 것도 없습니다.

고려해야 할 또 다른 포인트는 컴퓨터 메모리가 작동하는 방법입니다. 정수형이 1 ~ 4 바이트의 저장 공간을 차지할 수 있다고 가정 해 봅시다. 정수 42에 값 42를 저장한다고 가정하십시오. 1 바이트를 차지하고 메모리 주소 X에 저장합니다. 그런 다음 다음 변수를 X + 1 위치에 저장합니다 (이 시점에서 정렬을 고려하지 않음). . 나중에 가치를 6424로 변경하기로 결정했습니다.

그러나 이것은 단일 바이트에 맞지 않습니다! 그럼 어떻게 하시겠습니까? 나머지는 어디에 두 시나요? 이미 X + 1에 뭔가가 있으니 거기에 놓을 수 없습니다. 다른 곳? 나중에 어떻게 알게 될까요? 컴퓨터 메모리는 삽입 의미론을 지원하지 않습니다 : 당신은 단지 장소에 무언가를 배치하고 방을 만들기 위해 모든 것을 밀어 넣을 수 없습니다!

옆으로 : 당신이 말하는 것은 실제로 데이터 압축 영역입니다. 압축 알고리즘은 모든 것을 더 가깝게 압축하기 때문에 적어도 일부는 필요 이상으로 정수에 더 많은 공간을 사용하지 않을 것을 고려할 것입니다. 그러나 압축 된 데이터는 (가능하다면) 수정하기가 쉽지 않고 변경 작업을 수행 할 때마다 재 압축 되기만합니다.


최적화 및 단순화입니다.

고정 된 크기의 개체를 가질 수 있습니다. 따라서 값을 저장하십시오.
또는 가변 크기의 객체를 가질 수 있습니다. 그러나 가치와 크기를 저장하십시오.

고정 크기의 객체

숫자를 조작하는 코드는 크기에 대해 걱정할 필요가 없습니다. 당신은 항상 4 바이트를 사용하고 코드를 매우 단순하다고 가정합니다.

동적 크기 객체

숫자를 조작하는 코드는 값과 크기를 읽어야하는 변수를 읽을 때 이해해야합니다. 크기를 사용하여 모든 상위 비트가 레지스터에서 0이되도록하십시오.

값이 현재 크기를 초과하지 않은 경우 메모리에 값을 다시 배치하면 메모리에 값을 다시 배치하면됩니다. 그러나 값이 줄어들거나 커지면 오버플로가 발생하지 않도록 개체의 저장 위치를 ​​메모리의 다른 위치로 이동해야합니다. 이제 그 번호의 위치를 ​​추적해야합니다 (크기가 너무 커지면 이동할 수 있습니다). 또한 사용되지 않은 변수 위치를 추적하여 재사용 할 수 있어야합니다.

개요

고정 된 크기의 객체를 위해 생성 된 코드는 훨씬 간단합니다.

노트

압축은 255가 1 바이트에 맞을 것이라는 사실을 사용합니다. 다른 숫자에 대해 서로 다른 크기 값을 사용하는 대용량 데이터 세트를 저장하기위한 압축 스키마가 있습니다. 그러나 이것이 라이브 데이터가 아니기 때문에 위에 설명 된 복잡성이 없습니다. 저장을 위해 데이터를 압축 / 압축 해제하는 비용으로 적은 공간을 사용하여 데이터를 저장합니다.


컴퓨터 메모리는 특정 크기 (종종 8 비트 및 바이트라고 함)의 연속 주소 지정된 청크로 세분화되며 대부분의 컴퓨터는 연속 주소가있는 바이트 시퀀스에 효율적으로 액세스하도록 설계되었습니다.

객체의 주소가 객체의 수명 기간 내에서 변경되지 않으면 해당 주소가 지정된 코드가 문제의 객체에 신속하게 액세스 할 수 있습니다. 그러나이 접근법의 근본적인 한계는 주소가 X 주소에 할당되고 다른 주소가 N 바이트 떨어진 주소 Y에 할당되면 X는 수명 기간 동안 N 바이트보다 커질 수 없다는 것입니다 X 또는 Y가 이동하지 않는 한 Y의 X가 움직이려면 X의 주소를 보유하고있는 우주의 모든 것이 새로운 것을 반영하도록 업데이트되어야하며, 마찬가지로 Y가 이동해야합니다. 이러한 업데이트를 용이하게하는 시스템을 설계 할 수는 있지만 (Java와 .NET 모두 잘 관리 할 수 ​​있습니다.) 평생 동안 동일한 위치에 머물러있는 객체로 작업하는 것이 훨씬 효율적입니다. 일반적으로 크기를 반드시 지정해야합니다. 일정하다.







c++