[C언어] 11.메모리와 포인터
C언어 학습 노트 : 11. 메모리와 포인터#
1. 메모리 종류(프로세스 관점)#
Stack(스택)
- 함수 호출 시 생성되는 자동 변수 저장. LIFO.
- 각 스레드마다 독립 스택이 생성된다.
Heap(힙)
- 동적 할당 영역.
malloc/calloc/realloc/free로 관리한다.
- 동적 할당 영역.
실행 코드/데이터(이미지 섹션)
- Text(Code) section: 기계어 코드. 실행 전용.
- RO Data: 변경 불가 상수(문자열 리터럴 등).
- RW Data / BSS: 전역/정적 변수의 초기화된/미초기화 구역(정적 저장 영역).
주의: Windows는 PE, 리눅스/유닉스는 ELF 형식이 일반적이나, 개념(코드/데이터/정적 영역) 자체는 유사하다.
2. 가상 메모리#
- 가상 메모리는 “RAM + 디스크를 이어붙인다”라기보다, 각 프로세스에 연속적인 가상 주소 공간을 제공하고 이를 물리 메모리/백킹 스토어에 매핑하는 추상화이다.
- 스택/힙/코드/데이터 모두 프로세스의 가상 주소 공간 안에 존재한다.
3. 정적 영역#
- 전역 변수, 정적 지역 변수(
static), 문자열 리터럴(보통 RO) 이 여기에 위치한다. - 수명은 프로그램 시작부터 종료까지이다(정적 저장 기간).
4. 실행 코드 구조#
- 실행 파일은 여러 섹션(code, rodata, data, bss 등)으로 구분되며, 섹션별 보호 속성(실행/읽기/쓰기)이 다르고 메모리 배치도 구분되어 로드된다.
- (그림을 넣을 계획이라면: “Text → RO Data → Data/BSS → Heap → … ← Stack” 순의 개략도 권장)
5. 포인터 변수#
- 포인터는 주소를 저장하는 변수이다. 포인터 자체의 값은 “어디를 가리키는가”를 의미한다.
- 포인터가 가리키는 대상의 저장 위치는 스택/힙/정적 어느 곳이든 될 수 있다.
6. 포인터와 1차원 배열#
배열은 포인터가 아니다. 다만 대부분의 식에서 배열 이름이 “첫 원소의 포인터”로 변환(decay) 된다.
int arr[5];에서arr→ (대부분의 식에서)int*로 변환&arr→int (*)[5](배열 전체의 포인터, 타입이 다름)
인덱싱 등가식:
arr[i] == *(arr + i)크기 차이:
sizeof(arr) == 5*sizeof(int),sizeof(int*)는 보통 8바이트(64비트 환경) 등 서로 다르다.배열 이름은 재대입 불가(비상수 lvalue가 아님), 포인터 변수는 재대입 가능.
7. 메모리 동적 할당 및 관리#
- 힙 사용:
malloc(size)는 초기화하지 않은 메모리를,calloc(n,sz)는 0으로 초기화된 메모리를 반환한다. - 새로 할당받은 메모리는 읽기 전에 반드시 값을 써서 초기화해야 한다(미정 의 값 사용 금지).
- 용량이 매우 크면 0 초기화에 비용이 든다. 필요한 경우에만
calloc을 선택한다. - 해제는 반드시
free(p)로 대응한다(누수 방지).
8. 메모리 관리 단위#
- 페이지(page): 운영체제가 메모리를 관리하는 기본 단위(일반적으로 4KB).
- (Windows 특이점) 할당 그라뉴러리(allocation granularity) 64KB 개념이 있으나 이는 특정 API(VirtualAlloc) 레벨의 영역 관리 단위이다. C의
malloc사용자 입장에서는 페이지/슬랩/풀 등 할당기 내부 전략으로 추상화된다.
9. calloc#
- 동적 할당과 제로 초기화를 동시에 수행한다.
- 문자열 버퍼처럼 “초기값이 0이어야 의미가 명확한” 경우에 적합하다.
10. 메모리 값 복사/비교#
memcpy(dst, src, n): 바이트 단위 복사. 구조체 내부에 포인터가 있으면 “깊은 복사”가 아니다. (얕은 복사)memcmp(a, b, n): 바이트 단위 비교. 길이n을 반드시 명시한다.
문자열 관련#
strcpy(dst, src): NULL 종료 문자열 복사. 버퍼 크기 검증이 없으므로 위험.- MS 계열:
strcpy_s(dst, dstsz, src)(Annex K, 구현 의존) - POSIX/BSD 계열:
strlcpy(dst, src, dstsz)(비표준 확장) - 범용 대안:
snprintf(dst, dstsz, "%s", src)또는memcpy+ 길이 검사.
- MS 계열:
strcmp(a, b): NULL 종료 문자열 비교(크기 인자 없음).strstr(haystack, needle): 부분 문자열 검색. 찾으면 포인터 반환, 없으면NULL.인덱스가 필요하면:
char *p = strstr(buf, "am"); if (p) { size_t idx = (size_t)(p - buf); }
11. realloc()#
기존 블록 크기를 조정하여 새 포인터를 반환한다(이동·복사 발생 가능).
실패 시
NULL을 반환하고 원래 블록은 그대로이므로, 안전 패턴:void *tmp = realloc(p, new_size); if (tmp) p = tmp; else { /* 실패 처리 */ }빈번한 크기 변경은 복사 비용으로 인해 느려질 수 있다. 여유를 둔 성장 전략(예: 1.5–2배)을 고려한다.
12. 정적 메모리와 저장 기간/링케이지#
자동 변수(auto, 지역): 스택에 위치, 블록 종료 시 소멸.
정적 저장 기간(static storage duration): 프로그램 시작~종료(전역/정적).
static키워드- 블록 내부: 지역 변수를 정적 저장 기간으로 만든다(호출 간 값 유지).
- 파일 스코프: 변수/함수의 내부 링케이지(모듈 내부 전용) 부여. “모듈 전역”처럼 동작.
경고: 전역/정적 상태는 동시성 이슈와 테스트 어려움을 유발한다. 함수 내
static사용 시 스레드 안전성을 사전에 설계해야 한다.
13. extern#
다른 번역 단위(파일)에 정의가 존재함을 알리는 선언 지정자이다(외부 링케이지).
“external file의 줄임말”이 아니라 external linkage의 명시라는 점을 기억한다.
// a.c int g_counter = 0; // b.c extern int g_counter; // a.c의 정의를 사용
14. 문자열과 저장 영역 주의#
- 문자열 리터럴은 보통 읽기 전용 영역에 놓인다.
char *p = "abc"; *p = 'z';는 UB(정의되지 않은 동작) 가 될 수 있다. 수정이 필요하면char p[] = "abc";처럼 배열로 복사하여 사용한다.
학습 포인트 정리#
- 배열 ≠ 포인터. 배열 이름은 다수의 문맥에서 첫 원소 포인터로 decay 되지만 타입/크기/재대입 가능성에서 차이가 난다.
- 동적 메모리는 할당→초기화→사용→해제 생명주기를 지킨다.
- OS는 메모리를 페이지 단위로 관리하며,
realloc은 이동·복사가 발생 가능하므로 안전 패턴을 사용한다. - 문자열 API는 버퍼 크기 검증이 핵심이다(
strcpy지양, 안전 대안 사용). - 전역/정적 상태는 편리하지만 동시성·테스트·설계 복잡도를 높인다. 최소화가 원칙이다.
extern은 외부 정의 연결,static은 저장 기간 또는 내부 링케이지를 의미한다.