Journey to CS/임베디드

[메모리] 엔디언(Endianness)과 포인터 캐스팅(casting)

Cordilog 2025. 9. 14. 22:28

이번 포스팅에서는 메모리 주소와 관련된 개념인 엔디언포인터 캐스팅에 대해 다루어 보려고 합니다.

1. 엔디언(Endianness)이란?

컴퓨터 메모리는 바이트(byte) 단위로 구성되어 있습니다.

int와 같은 여러 바이트로 이루어진 데이터 타입을 메모리에 저장할 때, 바이트들의 순서를 정하는 방식이 바로 엔디언입니다.

엔디언에는 크게 두 가지 방식이 있습니다.

  • 빅 엔디언(Big Endian): 가장 큰 바이트(최상위 바이트)를 가장 작은 주소에 저장하는 방식입니다. 사람이 숫자를 읽는 방식과 유사해 직관적입니다.
  • 리틀 엔디언(Little Endian): 가장 작은 바이트(최하위 바이트)를 가장 작은 주소에 저장하는 방식입니다. 대부분의 x86 아키텍처(인텔, AMD 등)가 이 방식을 사용합니다.

예) 16진수 값인 int a = 0x12345678;은 메모리에 어떻게 저장될까요?

int a = 0x12345678;은 4바이트 값이고, 16진수 두 자리(예: 0x12)가 각각 1바이트씩입니다.

*참고로 0x는 16진수임을 표시하기 위한 라벨 같은 것으로, 사람이 보기 위한 표시일 뿐 메모리에는 저장이 되지 않음.

 

  • 16진수 한 자리 = 4비트
    16진수 두 자리 = 8비트 = 1바이트

 

따라서 32비트 정수(8개의 16진수 자리)는 두 자리씩 네 묶음으로 나뉘어 네 바이트가 되는 것입니다.

0x12  0x34  0x56  0x78   ← 4개의 바이트

 

엔디언은 바로 이 4바이트 값을 1바이트씩 어떤 순서로 메모리에 배치할 것인지를 정합니다.

그래서 빅 엔디언 방식을 적용하면 다음 그림과 같이 낮은 주소에 상위 바이트부터 순서대로 저장이 됩니다. 

 

이와 반대로 리틀 엔디언 방식을 따른다면, 낮은 주소에 하위 바이트부터 저장이 될 것이므로 다음과 같은 모양이 되겟죠. 

 

2. 포인터와 포인터 캐스팅(casting)

int main(void)
{
	int a = 0x12345678;
	int *ptr;
    
  	ptr = &a;
    
}

 

포인터는 메모리 주소를 저장하는 변수입니다.

위 코드에서 ptrint *ptr로 선언되어 int 변수 a의 주소를 담고 있습니다.

이 포인터를 활용하면 변수 a가 저장된 메모리 위치에 직접 접근할 수 있습니다.

 

먼저 &ptr의 값을 구하는 과정은 아래 그림을 통해 쉽게 이해할 수 있을 것입니다.

(빅 엔디언인 경우로 가정)

 

포인터 캐스팅(pointer casting)이란?

사실 여기서 더 중요한 개념이 포인터 캐스팅입니다. 

ptr은 int형 포인터이므로, 원래는 4바이트 단위로 데이터를 참조합니다.

하지만 (short *)ptr와 같이 캐스팅하면, ptr이 가리키는 주소를 short형(2바이트)으로 해석하라는 의미가 됩니다.

 

여기서 주의할 점은, (short*)ptr로 캐스팅을 한다고 해서 실제로 메모리에 있는 값이 변하는 것이 아니라는 점입니다.

int *ptr; 라고 선언했으면 ptr의 타입은 컴파일 타임에 이미 int*로 고정되기 때문에 런타임에 캐스팅으로 변수의 선언 타입을 바꾸는 일은 없습니다.

캐스팅은 단지 "타입만 short*로 바꿔서 보겠다"는 뜻입니다.
즉, 주소나 메모리에 저장된 값은 안 바뀌고, 컴파일러가 그 주소를 2바이트 단위(=short) 로 해석하도록 지시하는 표현식의 타입 변경입니다.

 

그러면 값도 안 바뀌는데 포인터 캐스팅을 왜 하는걸까요?

바로 역참조, 포인터 연산 등을 할 때 단위의 해석이 달라지기 때문입니다. 

 

1) 역참조를 할 때 읽어오는 크기가 달라진다

 

  • *ptr4바이트(int) 읽음
  • *(short*)ptr2바이트(short) 읽음
  • *(char*)ptr1바이트(char) 읽음

 

특히 위에서 알아본 엔디언에 의해 *(short*)ptr의 값이 달라집니다. 

  • Big endian 적용 : *(short*)ptr == 0x1234 (최상위 2바이트)
  • Little endian 적용 : *(short*)ptr == 0x5678 (최하위 2바이트)

2) 포인터 산술 단위(얼마만큼씩 이동하는지)가 달라진다

 

포인터에 +1을 하면 타입의 크기만큼 주소가 전진합니다.

  • (int*)ptr + 1 → +4바이트 (다음 int)
  • (short*)ptr + 1 → +2바이트 (다음 short)
  • (char*)ptr + 1 → +1바이트 (다음 바이트)

따라서, (short*)ptr + 1 과 (char*)ptr + 2는 같은 주소를 가리키게 됩니다.

 

지금까지 엔디언과 포인터 캐스팅에 대해 알아보았습니다.

이 두 개념은 로우 레벨(low-level) 프로그래밍에서 매우 중요합니다.

특히 네트워크 통신이나 파일 입출력처럼 서로 다른 시스템 간에 데이터를 주고받을 때 엔디언 문제가 발생할 수 있으며, 이를 해결하기 위해 포인터 캐스팅을 적절히 활용해야 합니다.