16.1 동적 메모리와 자기참조 구조체
정적(static) 메모리 할당: 컴파일 이전에 저장공간 수나 크기를 정한 메모리 할당 방법
프로그램이 실행되기 이전에 변수의 저장 공간 크기가 정해지고 프로그램 또는 함수가 시작되면 메모리에 할당되어 사용되며 그 모듈이나 프로그램이 종료되면 변수가 메모리에서 삭제되는 방식
메모리의 사용 예측이 부정확한 경우 충분한 메모리를 미리 확보해야 하므로 비효율적임
동적(dynamic) 메모리 할당: 실행 중에 메모리를 할당하는 방식
함수 malloc() 등을 사용하여 메모리 사용 예측이 정확하지 않고 실행 중에 메모리 할당이 필요한 경우 적합하다
malloc의 호출로 힙(heap) 영역에 확보됨
동적으로 할당한 메모리는 사용 후 함수 free()를 사용해 해제해야 함
malloc(), calloc(), realloc() 3가지가 있는데 모두 반환형(void *)로 메모리 할당에 요구한 자료의 포인터형으로 변환해야 함
stdlib.h 헤더파일
1. 메모리 할당
- 기본값
없음: void *malloc(size_t)
인자만큼의 메모리 할당 후 기본 주소 반환
0: void *calloc(sise_t, size_t)
뒤 인자 만큼의 메모리 크기로 앞 인자 수 만큼 할당 후 기본 주소 반환
2. 기존 메모리 변경
이전 값: void* realloc(void *, size_t)
앞 인자의 메모리를 뒤 인자 크기로 변경 후, 기본 주소 반환
3. 메모리 해제
해당 없음: void free(void *)
인자를 기본 주소로 갖는 메모리 해제
메모리 할당 영역
메모리 영역이 데이터(static data) 영역, 힙(heap) 영역, 스택(stack) 영역 세 부분으로 나뉨
동적 메모리가 할당되는 부분이 힙 영역, 데이터 영역은 전역변수와 정적변수가 할당되는 저장공간
스택 영역은 함수 호출에 의한 형식 매개변수 그리고 함수 내부의 지억변수가 할당되는 저장공간
int *i = (int*) malloc (sizeof(int));
포인터 변수 i가 지역 변수라면 i는 스택에 할당되고 malloc()에 의한 메모리 공간은 힙 영역에 할당된 후, malloc()의 반환값인 주소값이 i에 저장
함수 malloc()
동적 메모리 할당을 위해서는 함수 malloc()을 이용하여 메모리 공간을 확보해야 한다
void *malloc(size_t size);
size만큼 메모리를 할당하여 성공하면 할당된 공간의 void 포인터를 반환하며, 실패하면 NULL 반환
함수 malloc()은 인자로 메모리 할당의 크기를 지정하고 할당된 메모리의 시작 주소를 반환
반환 값의 유형은 모든 자료형의 포인터로 이용할 수 있도록 void *를 사용
함수 free()
함수 malloc()에 의하여 동적으로 할당된 메모리 공간은 더 이상 필요가 없거나 프로그램을 종료하는 경우에 반드시 메모리를 해제해야 함
stdlib.h에 저장되어 있음
함수 malloc()에 의한 배열 공간 할당
int *ary;
ary = (int *) malloc(sizeof(int) * 3);
이런식으로 배열 형태로 사용할 수 있음
malloc은 C에서 void*가 자동으로 형변환이 되어서 캐스팅을 할 필요가 없다 그러나 C++에서는 캐스팅을 해줘야 한다
함수 calloc()
메모리 공간을 확보하고 초기값을 자료형에 알맞게 0을 저장
함수 calloc()은 함수 원형이 stdlib.h에 저장되어 있다
함수 calloc()에서 앞의 인자는 할당되는 원소의 개수이고 뒤의 인자는 한 원소의 크기이다
calloc(할당할 개수, 할당될 메모리의 크기)
함수 realloc()
이미 확보한 저장공간을 새로운 크기로 변경
함수 realloc()에 의하여 다시 확보하는 영역은 새로운 영역을 다시 할당하여 이전의 값을 복사하거나 기존의 영역을 이용하여 그 저장 공간을 변경하기도 함
realloc()은 성공적으로 메모리를 할당하면 변경된 저장공간의 시작 주소를 반환하고 실패하면 NULL을 반환
첫 번째 인자는 변경할 저장공간의 주소이며 두 번째 인자는 변경하고 싶은 저장 공간의 총 크기
realloc()에 의하여 확장되는 공간은 malloc()과 같이 기본값 0이 저장되지 않음
함수 realloc()은 이미 확보한 메모리 p를 다시 지정한 크기 size로 변경하는 함수
NULL만 예외적으로 realloc(NULL, size_t)를 쓰면 malloc과 같은 기능을 한다
자기참조 구조체
자기참조 구조체(self reference struct): 구조체의 멤버 중 하나가 자기 자신의 구조체 포인터 변수를 갖는 구조체
구조체는 자기 자신 포인터를 멤버로 사용할 수 있으나 자기 자신은 멤버로 사용할 수 없다 구조체 정의 구문에서 자기 자신 구조체 유형의 멤버는 허용하지 않음
자기참조 구조체는 동일 구조체의 표현을 여러 개 만들어 연결할 수 있는 기능을 가짐. 이러한 구조를 연결 리스트(linked list)라고 함
구조체 *first의 멤버 next에 구조체 포인터 second의 내용인 주소값을 저장
16.2 연결 리스트
배열은 배열 이름과 첨자(index)를 사용하여 원하는 위치의 원소를 직접 임의 참조(random access) 가능함
컴파일 전에 배열의 크기가 이미 결정되어야 하고 실행 중간에 배열 크기를 늘릴 수 없다는 단점이 있음
중간이 이미 모든 원소로 채워져 있는 배열에서 맨 앞이나 중간에 새로운 자료를 삽입하려면 삽입되는 자료 이후의 원소가 모두 이동해야 하는 번거로움이 있음
연결 리스트(linked list)는 원소인 노드(node)가 순차적으로 연결된 자료구조
첨자 대신 링크(link)라는 포인터로 다음 노드를 가리키는 구조이므로 노드는 자료(data)와 링크(link)로 구성
헤드(head)는 항상 첫 번째 노드를 가리키는 포인터
연결 리스트에서 마지막 노드의 링크는 NULL로 저장
연결 리스트에 노드가 하나도 없다면 헤드는 NULL을 가짐
연결 리스트의 가장 큰 장점은 프로그램 내부에서 메모리가 허용하는 한 연결 리스트 항목 수를 늘릴 수 있다는 것
연결 리스트는 프로그램 실행 중이라도 필요할 때 노드를 동적으로 생성하여 기존의 연결 리스트에 삽입 또는 추가할 수 있음
연결 리스트는 기억장소를 비순차적으로 사용한다
연결 리스트는 동적으로 노드를 생성하므로 연결 리스트의 크기의 증가 감소에 따라 효율적으로 대처할 수 있으며 노드의 삽입과 삭제와 같은 자료의 재배치를 빠르게 처리할 수 있음
연결 리스트는 배열에 비하여 임의 접근(random access)에 많은 시간이 소요되는 단점이 있음
연결 리스트에서 모든 노드를 순서대로 참조하는 방법을 노드 순회(node traversal)
링크가 NULL이면 마지막 노드임을 알 수 있음
새로운 노드를 하나 생성하여 연결 리스트의 마지막 노드로 추가하는 방법도 연결 리스트의 주요 기능
노드 추가
1. 새로운 노드를 추가하기 위해서는 첫 번째로 추가할 노드를 먼저 생성한 후, 자료를 저장하고 링크를 NULL로 저장
2. 헤드에서부터 포인터를 따라 기존 연결 리스트를 순회하여 마지막으로 이동
3. 마지막으로 이동한 노드의 링크를 새로 생성한 노드의 주소값으로 저장하여 연결 리스트의 마지막 노드로 연결
노드 삽입
1. 가장 먼저 삽입 노드를 동적으로 생성하여 원하는 자료를 저장
2. 헤드 포인터를 따라 가며 삽입하려는 바로 이전 노드인 노드로 이동
3. 삽입하는 노드의 링크에 다른 노드를 저장
4. 다음에는 노드 C의 링크를 새로 삽입하는 Objective-C 노드를 가리키도록 삽입하는 주소값을 저장
노드 삭제
1. 연결 리스트에서 노드 C++를 삭제하려면 가장 먼저 삭제하려는 노드 바로 이전 노드 C로 이동해야 한다. 또는 삭제 이전 노드를 바로 알려줄 수도 있다.
2. 포인터 변수(del)를 마련해 삭제할 이전 노드의 링크인 삭제하려는 노드 C++의 주소를 저장
3. 노드 C의 링크를 수정하기 위해 노드 C의 링크를 삭제하려는 노드 C++의 링크 노드 값으로 저장
4. 이제 포인터 변수 del로 삭제 노드 C++를 메모리에서 제거
16.3 전처리
다양한 전처리 명령어
조건부 컴파일 문장은 개발단계에서만 실행에 참여하는 문장처리에 적합
전처리기는 조건부로 필요한 문장을 컴파일에 참여시키는 지시자를 제공
#include 지정된 헤더파일 내용을 현재에 복사
예약 매크로
ANSI 표준 매크로로 프로그램 디버깅에 활용될 수 있다
조건부 컴파일 지시자
#if 다음의 조건식 expression이 0이 아니면 #endif 또는 다른 옵션의 전처리 지시자 사이의 모든 문장을 컴파일하고 만일 조건식 expression이 0이면 컴파일에서 제거
#if는 반드시 #endif로 종료
#ifdef와 #endif 사이에는 C 문장 뿐만 아니라 #define과 같은 다른 전처리기 지시자도 올 수 있으며 옵션으로 else if를 의미하는 #elif와 else인 #else가 올 수 있음
#if #endif는 일반 if문과 유사 다만 #if에서는 조건식에 괄호가 생략 가능하며 조건 절의 문장이 여러 개라도 블록을 사용할 필요가 없다 그러나 #if의 조건식에 괄호를 사용해도 무방
명령문 #if 조건식에는 기호 상수와 정수 상수, 문자 상수만 나올 수 있으며 그 결과도 반드시 정수여야 함
실수 상수와 문자열 상수, 변수 등을 사용할 수 없음
조건식에는 관계연산자와 논리연산자 그리고 사칙연산을 사용할 수 있음
조건식에 변수는 사용할 수 없음
C파일 컴파일 과정은 순서가
1. 전처리기
2. 컴파일러
3. 링커
이런식으로 진행되어서 전처리기의 매크로 인자는 따옴표 없이 입력할 수 있는것임
전처리 연산자 defined
전처리 연산자 defined(기호상수)는 기호상수가 정의되었다면 참을 의미하는 0이 아닌 값을, 정의가 되지 않았다면 거짓을 의미하는 0을 반환
#if (defined WINDOWS) // 여기서 괄호는 생략 가능
typedef long my_int;
#ifdef
- # 다음에 나오는 기호상수가 이미 정의되었다면 #endif까지 모든 문장을 컴파일하고, 그렇지 않으면 컴파일에서 제거
#ifndef
기호상수가 정의되지 않았다면 #endif까지 ~
#undef
이미 정의된 기호 상수를 해지하는 지시자
전처리 연산자 종류
#, #@, ##, defined
# 문자열 만들기 연산자 (Stringizing operator) 인자 앞 뒤에 큰따옴표를 붙여 인자를 문자열로 만드는 연산자
#@ 문자 만들기 연산자 (charzing operator) 인자 앞 뒤에 작은따옴표를 붙여 인자를 문자로 만드는 연산자
## 토큰 붙이기 연산자 (token-pasting operator) 인자를 다른 토큰들과 연결해주는 연산자
defined 정의 검사 연산자 (defined operator) 상수로 정의되어 있는지 검사하는 연산자
\는 매크로 정의 줄 이어쓰기
define은 매크로 함수(function-like macro)를 만드는 데도 쓴다