C언어로 배열을 선언할 때는 문자열을 넣어 초기화할 수 있지만 나중에 배열명에 직접 대입을 해보면 컴파일 에러가 난다.
결론부터 말하자면 이는 배열명이 해당 배열의 시작 주소를 가리키는 상수(constant)이기 때문에 다른 주소를 대입할 수 없기 때문이다.
1. 초기화(Initialization) vs 대입(Assignment)
C 언어에서 = 연산자는 문맥에 따라 완전히 다른 의미를 갖는다.
1️⃣선언 시 초기화: char ch[] = "ABCDEF";
이 구문은 변수를 생성함과 동시에 값을 채우는 과정이다.
컴파일러는 다음과 같은 작업을 수행한다.
- 메모리(스택 영역)에 7바이트(문자 6개 + NULL) 크기의 공간을 확보한다.
- 확보한 공간에 ch라는 이름을 붙인다.
- 해당 공간에 'A', 'B', 'C', 'D', 'E', 'F', '\0' 데이터를 순서대로 복사해서 채워 넣는다.
2️⃣선언 후 대입: ch = "ABCDEF"; (컴파일 에러)
이미 선언된 배열 이름 ch에 문자열을 넣으려고 하면 에러가 발생한다.
C언어에서 배열의 이름은 그 자체로 해당 배열의 시작 주소를 가리키는 '상수(Constant)'이기 때문이다.
- "ABCDEF"라는 문자열 리터럴은 메모리의 'Read-only 데이터 영역'에 따로 존재한다.
- ch = "ABCDEF"라는 코드는 "배열 ch의 시작 주소 자체를 문자열 리터럴이 있는 주소로 바꿔라"라는 의미인데, 배열은 자신의메모리상의 위치를 임의로 변경할 수 없다.
2. 문자열 저장의 두 가지 방식: 배열명과 포인터
배열과 포인터가 메모리에서 어떻게 다르게 관리되는지 이해해야 한다.

- 배열 (ch): 주소 0x100이라는 위치가 이름표처럼 붙어 있다. 이 위치 자체를 수정(대입)할 수 없다.
- 포인터 (ptr): 메모리 주소값을 담는 변수이므로, 언제든지 다른 주소(예: 0x500)를 가리키도록 대입이 가능하다.
🟣코드 예시
#include <stdio.h>
int main() {
char ch[] = "ABCDEF";
char *ptr = "123456";
printf("Array ch address: %p\n", (void*)ch);
printf("Pointer ptr points to: %p\n", (void*)ptr);
return 0;
}
🟢배열: 공간을 직접 소유하기
char ch[] = "ABCDEF"; 코드는 스택(Stack) 영역에 7바이트의 공간을 만들고, 그 안에 문자 데이터를 직접 복사한다.
배열의 이름 ch는 이 공간의 시작 주소를 의미하는 상수이므로 나중에 ch = "New"와 같이 주소 자체를 바꾸려는 시도는 불가능하다.
🟢포인터: 주소값만 저장하기
char *ptr = "123456"; 코드는 메모리의 읽기 전용(Read-only) 영역에 저장된 문자열 "123456"의 첫 번째 주소값을 ptr이라는 변수에 저장한다. ptr은 데이터를 직접 들고 있는 것이 아니라 데이터가 있는 곳을 가리키는 '이정표' 역할을 한다.
3. GDB로 실제 메모리 분석하기
먼저 gdb에 들어가서 마지막 실행문에 중단점을 걸고 디버그를 실행한다.

1️⃣주소값 비교
배열명(ch)와 포인터(ptr)의 주소값을 각각 찍어본다.

- 배열 주소 (ch): 0x7fffffffdd41 (스택 영역의 높은 주소)
- 포인터가 가리키는 주소 (ptr): 0x555555556004 (데이터 영역의 낮은 주소)
두 주소의 체계가 완전히 다르다는 점이 핵심이다.
배열은 자신이 선언된 스택 위치를 고수하지만, 포인터는 멀리 떨어진 데이터 영역의 주소를 담고 있다.
2️⃣메모리 내부 데이터 확인 (x 명령)
GDB의 x/2xw 명령(4바이트씩 16진수로 출력)을 사용하면 메모리에 데이터가 어떻게 들어있는지 볼 수 있다.
① 배열(ch): 이름이 곧 데이터의 시작점
배열은 이름 자체가 곧 데이터가 시작되는 지점을 가리킨다.

- ch의 주소와 첫 번째 요소 &ch[0]의 주소가 일치한다.
- 해당 주소를 열자마자 0x44434241이라는 값이 나온다. 이는 ASCII 코드로 41(A), 42(B), 43(C), 44(D)를 의미한다. (리틀 엔디안 방식이라 역순으로 보임)
- 결론: 별도의 주소 저장 공간 없이, ch라는 이름의 메모리 위치에 문자열 데이터가 직접 저장되어 있음을 확인할 수 있다.
② 포인터(ptr): 주소를 담기 위한 별도의 공간
반면 포인터 변수는 데이터로 가기 위한 주소를 저장하는 별도의 공간을 가진다.


포인터가 가진 주소인 0x555555556004로 찾아가야 비로소 문자열 "123456"에 해당하는 데이터(0x34333231 등)를 확인할 수 있다.
4. 요약: 왜 ch = "ABC"가 안 되는가?
디버깅 결과에서 보듯 두 변수의 메모리 성격은 완전히 다르다.
| 구분 | 메모리 내용 (Value) | 데이터 도달 방식 |
| 배열 (ch) | 실제 문자 데이터 ('A', 'B'...) | 즉시 접근 (해당 위치가 곧 데이터) |
| 포인터 (ptr) | 다른 곳의 주소 (0x5555...) | 간접 접근 (주소를 읽고 해당 위치로 이동) |
배열명 ch는 메모리 주소 0x7fffffffdd41을 가리키는 고정된 라벨이다.
ch = "New"라고 쓰는 것은 이 라벨이 붙은 물리적인 위치 자체를 옮기라는 말도 안 되는 명령이 되기 때문에 컴파일러가 에러를 뱉는 것이다.
반면 포인터(ptr)는 0x7fffffffdd38이라는 칸에 들어있는 '주소 쪽지'만 갈아 끼우면 되므로 대입이 자유로운 것이다.
'Journey to Security > C언어' 카테고리의 다른 글
| GDB TUI 모드: 소스 코드를 보며 디버깅하기 (0) | 2026.02.23 |
|---|---|
| C 포인터 배열과 이중 포인터의 메모리 구조 (0) | 2026.02.23 |
| C언어 배열 메모리 레이아웃 (feat. GDB) (0) | 2026.02.20 |
| 비트 연산(Bitwise Operation) + 2의 보수 개념 (0) | 2026.02.15 |
| C 주소 전달 메커니즘: scanf 원형과 작동 원리, 메모리 구조 (0) | 2026.02.14 |