Namespaces
Variants

Objects and alignment

From cppreference.net

C 프로그램은 객체를 생성, 파괴, 접근 및 조작합니다.

C에서 객체는 실행 환경에서의 데이터 저장 영역 으로, 그 내용이 을 나타낼 수 있습니다 (값은 특정 타입 으로 해석될 때 객체 내용의 의미입니다).

모든 객체는

  • 크기 ( sizeof 로 결정 가능)
  • 정렬 요구사항 ( _Alignof (C23 이전) alignof (C23 이후) 로 결정 가능) (C11 이후)
  • 저장 기간 (자동, 정적, 할당된, 스레드-지역)
  • 수명 (저장 기간과 동일하거나 임시적)
  • 유효 타입 (아래 참조)
  • 값 (불확정적일 수 있음)
  • 선택적으로, 이 객체를 나타내는 식별자 .

객체는 선언 , 할당 함수 , 문자열 리터럴 , 복합 리터럴 , 그리고 배열 멤버를 가진 구조체나 공용체 를 반환하는 비-lvalue 표현식에 의해 생성됩니다.

목차

객체 표현

비트 필드 를 제외하고, 객체는 하나 이상의 바이트로 구성된 연속적인 시퀀스로 이루어지며, 각 바이트는 CHAR_BIT 비트로 구성됩니다. 그리고 memcpy 를 사용하여 unsigned char [ n ] 타입의 객체로 복사할 수 있습니다. 여기서 n 은 객체의 크기입니다. 결과 배열의 내용은 객체 표현 으로 알려져 있습니다.

두 객체가 동일한 객체 표현을 가지고 있다면, (부동소수점 NaN인 경우를 제외하고) 동일하게 비교됩니다. 역은 성립하지 않습니다: 동일하게 비교되는 두 객체가 서로 다른 객체 표현을 가질 수 있습니다. 객체 표현의 모든 비트가 값에 참여해야 하는 것은 아니기 때문입니다. 이러한 비트들은 정렬 요구 사항을 충족하기 위한 패딩, 패리티 검사, 트랩 표현 표시 등에 사용될 수 있습니다.

객체 표현이 객체 타입의 어떤 값도 나타내지 않으면, 이를 트랩 표현(trap representation) 라고 합니다. 문자 타입의 lvalue 표현을 통해 읽는 것 이외의 어떤 방식으로든 트랩 표현에 접근하는 것은 정의되지 않은 행위입니다. 특정 멤버가 트랩 표현일지라도 구조체나 공용체의 값 자체는 절대 트랩 표현이 아닙니다.

char , signed char , 그리고 unsigned char 타입의 객체에 대해, 객체 표현의 모든 비트는 값 표현에 참여해야 하며, 가능한 모든 비트 패턴은 서로 다른 값을 나타내야 합니다(패딩, 트랩 비트, 또는 다중 표현이 허용되지 않음).

정수형 ( short , int , long , long long ) 객체가 여러 바이트를 차지할 때, 이러한 바이트의 사용은 구현에 따라 정의되지만 두 가지 주요 구현 방식은 빅 엔디안(big-endian) (POWER, Sparc, Itanium)과 리틀 엔디안(little-endian) (x86, x86_64)입니다: 빅 엔디안 플랫폼은 정수가 차지하는 저장 영역의 가장 낮은 주소에 가장 중요한 바이트(MSB)를 저장하고, 리틀 엔디안 플랫폼은 가장 낮은 주소에 가장 덜 중요한 바이트(LSB)를 저장합니다. 자세한 내용은 엔디안(Endianness) 를 참조하십시오. 아래 예제도 참조하십시오.

대부분의 구현에서는 트랩 표현(trap representation), 패딩 비트(padding bits), 또는 정수 타입에 대한 다중 표현을 허용하지 않지만, 예외가 있습니다; 예를 들어 Itanium의 정수 타입 값은 트랩 표현일 수 있습니다 .

유효 타입

모든 객체는 effective type 을 가지며, 이것은 어떤 lvalue 접근이 유효하고 어떤 접근이 strict aliasing 규칙을 위반하는지 결정합니다.

객체가 선언 에 의해 생성된 경우, 해당 객체의 선언된 타입이 객체의 유효 타입 입니다.

객체가 allocation function (including realloc )에 의해 생성된 경우, 선언된 타입을 가지지 않습니다. 이러한 객체는 다음과 같이 effective type을 획득합니다:

  • 문자 타입이 아닌 타입을 가진 lvalue를 통해 해당 객체에 첫 번째 쓰기를 수행할 때, 그 lvalue의 타입이 이 객체의 유효 타입(effective type) 이 되며, 이는 해당 쓰기와 이후의 모든 읽기에 적용됩니다.
  • memcpy 또는 memmove 를 사용하여 다른 객체를 해당 객체로 복사하거나, 다른 객체를 문자 타입 배열로 해당 객체에 복사할 때, 소스 객체의 유효 타입(존재하는 경우)이 이 객체의 유효 타입이 되며, 이는 해당 쓰기와 이후의 모든 읽기에 적용됩니다.
  • 선언된 타입이 없는 객체에 대한 다른 모든 접근에서, 유효 타입은 접근에 사용된 lvalue의 타입입니다.

엄격한 별칭 지정

effective type T1을 가진 객체가 주어졌을 때, 다른 타입 T2의 lvalue 표현식(일반적으로 포인터를 역참조하는 경우)을 사용하는 것은 다음의 경우를 제외하고 미정의 동작입니다:

  • T2와 T1은 호환 가능한 타입 입니다.
  • T2는 T1과 호환 가능한 타입의 cvr 한정 버전입니다.
  • T2는 T1과 호환 가능한 타입의 부호 있는(signed) 또는 부호 없는(unsigned) 버전입니다.
  • T2는 멤버 중에 (하위 집계체나 포함된 공용체의 멤버를 재귀적으로 포함하여) 앞서 언급된 타입 중 하나를 포함하는 집계 타입이나 공용체 타입입니다.
  • T2는 문자 타입 ( char , signed char , 또는 unsigned char ) 입니다.
int i = 7;
char* pc = (char*)(&i);
if (pc[0] == '\x7') // char를 통한 별칭 사용은 허용됨
    puts("이 시스템은 리틀 엔디안입니다");
else
    puts("이 시스템은 빅 엔디안입니다");
float* pf = (float*)(&i);
float d = *pf; // UB: float lvalue *p는 int 접근에 사용할 수 없음

이러한 규칙들은 두 개의 포인터를 받는 함수를 컴파일할 때, 컴파일러가 한 포인터를 통해 쓰기 작업 후 다른 포인터를 다시 읽는 코드를 생성해야 하는지 여부를 제어합니다:

// int*와 double*는 별칭이 될 수 없음
void f1(int* pi, double* pd, double d)
{
    // *pi로부터의 읽기는 루프 전에 한 번만 수행될 수 있음
    for (int i = 0; i < *pi; i++)
        *pd++ = d;
}
struct S { int a, b; };
// int*와 struct S*는 S가 int 타입 멤버를 가진 집합 타입이므로 서로 별칭이 될 수 있음
void f2(int* pi, struct S* ps, struct S s)
{
    // *pi로부터의 읽기는 *ps를 통한 모든 쓰기 이후에 발생해야 함
    for (int i = 0; i < *pi; i++)
        *ps++ = s;
}

restrict 한정자 는 위 규칙들이 별칭을 허용하더라도 두 포인터가 별칭이 아님을 나타내는 데 사용될 수 있습니다.

타입 펀닝(type-punning)은 union 의 비활성 멤버를 통해서도 수행될 수 있음에 유의하십시오.

정렬

모든 완전한 객체 타입 정렬 요구 사항 이라는 속성을 가지며, 이는 size_t 타입의 정수 값으로서, 해당 타입의 객체가 할당될 수 있는 연속적인 주소 사이의 바이트 수를 나타냅니다. 유효한 정렬 값은 음수가 아닌 2의 거듭제곱 정수입니다.

타입의 정렬 요구 사항은 _Alignof (until C23) alignof (since C23) 로 조회할 수 있습니다.

(since C11)

구조체의 모든 멤버에 대한 정렬 요구사항을 충족시키기 위해, 일부 멤버 뒤에 패딩이 삽입될 수 있습니다.

#include <stdalign.h>
#include <stdio.h>
// struct S 객체는 모든 주소에 할당될 수 있음
// S.a와 S.b 모두 모든 주소에 할당될 수 있기 때문
struct S
{
    char a; // 크기: 1, 정렬: 1
    char b; // 크기: 1, 정렬: 1
}; // 크기: 2, 정렬: 1
// struct X 객체는 4바이트 경계에 할당되어야 함
// X.n이 4바이트 경계에 할당되어야 하기 때문
// int의 정렬 요구사항이 (일반적으로) 4이기 때문
struct X
{
    int n;  // 크기: 4, 정렬: 4
    char c; // 크기: 1, 정렬: 1
    // 3바이트 패딩
}; // 크기: 8, 정렬: 4
int main(void)
{
    printf("sizeof(struct S) = %zu\n", sizeof(struct S));
    printf("alignof(struct S) = %zu\n", alignof(struct S));
    printf("sizeof(struct X) = %zu\n", sizeof(struct X));
    printf("alignof(struct X) = %zu\n", alignof(struct X));
}

가능한 출력:

sizeof(struct S) = 2
alignof(struct S) = 1
sizeof(struct X) = 8
alignof(struct X) = 4

각 객체 타입은 해당 타입의 모든 객체에 대해 정렬 요구 사항을 부과합니다. 가장 약한(가장 작은) 정렬은 char , signed char , 그리고 unsigned char 타입의 정렬이며 1 과 같습니다. 가장 엄격한(가장 큰) 기본 정렬(fundamental alignment) 은 구현에 따라 정의되며 max_align_t 의 정렬과 동일합니다 (C11부터) .

기본 정렬은 모든 종류의 저장 기간을 가진 객체에 대해 지원됩니다.

객체의 정렬이 max_align_t 보다 더 엄격하게(더 크게) 지정된 경우, _Alignof (until C23) alignof (since C23) 를 사용하여, 이는 확장 정렬 요구사항(extended alignment requirement) 을 가집니다. 확장 정렬을 가진 멤버를 포함하는 구조체나 공용체 타입은 과잉 정렬 타입(over-aligned type) 입니다. 과잉 정렬 타입의 지원 여부는 구현에 따라 정의되며, 각 저장 기간(storage duration) 마다 지원이 다를 수 있습니다.

구조체나 공용체 타입 S 가 과잉 정렬 타입의 멤버를 포함하지 않거나 확장 정렬을 지정하는 정렬 지정자로 선언되지 않은 경우, S 는 기본 정렬(fundamental alignment)을 가집니다.

모든 산술(arithmetic) 타입이나 포인터(pointer) 타입의 atomic 버전은 기본 정렬을 가집니다.

(since C11)

결함 보고서

다음 동작 변경 결함 보고서는 이전에 발표된 C 표준에 소급 적용되었습니다.

DR 적용 대상 게시된 동작 올바른 동작
DR 445 C11 _Alignas 없이도 타입이 확장 정렬을 가질 수 있음 기본 정렬을 가져야 함

참고문헌

  • C17 표준 (ISO/IEC 9899:2018):
  • 3.15 객체 (p: 5)
  • 6.2.6 타입 표현 (p: 33-35)
  • 6.2.8 객체 정렬 (p: 36-37)
  • 6.5/6-7 표현식 (p: 55-56)
  • C11 표준 (ISO/IEC 9899:2011):
  • 3.15 객체 (p: 6)
  • 6.2.6 타입 표현 (p: 44-46)
  • 6.2.8 객체 정렬 (p: 48-49)
  • 6.5/6-7 표현식 (p: 77)
  • C99 표준 (ISO/IEC 9899:1999):
  • 3.2 alignment (p: 3)
  • 3.14 object (p: 5)
  • 6.2.6 Representations of types (p: 37-39)
  • 6.5/6-7 Expressions (p: 67-68)
  • C89/C90 표준 (ISO/IEC 9899:1990):
  • 1.6 용어 정의

참고 항목

C++ 문서 for Object