배열의 기본 원리는 포인터 배열에서도 동일하게 적용된다.
1. 동일한 자료형으로 구성된다.
2. 연속된 메모리 공간이 할당된다.
배열 선언 방식:
자료형 배열명[크기];
포인터 배열은 자료형 옆에 *를 붙여 포인터의 배열을 만들 수 있다.
자료형* 배열명[크기];
포인터 자료형에는 주소를 저장할 수 있으며, 배열처럼 인덱스를 통해 접근할 수 있다.
일반적인 다차원 배열을 사용할 경우, 메모리 공간이 비효율적으로 사용될 수 있다.
예제:
char names[5][20] = { "hi", "hello", "goodbye", "welcome", "thanks" };
위의 예제에서 names는 5개의 문자열을 저장할 수 있도록 100바이트(5×20)를 고정적으로 할당받는다. 그러나 실제로 사용되지 않는 부분이 많아 내부 단편화가 발생하고, 메모리 효율이 저하된다.
포인터 배열을 사용하면, 필요한 만큼만 메모리를 할당할 수 있다.
char *names[5] = { "hi", "hello", "goodbye", "welcome", "thanks" };
각각의 문자열은 메모리 어딘가에 저장되며, names 배열에는 문자열의 시작 주소만 저장된다. 따라서 불필요한 공간 낭비를 줄일 수 있다.
문자열 리터럴("")은 실제로 문자열의 시작 주소를 의미한다. 따라서 if (name == "end") 같은 비교를 수행하면, 문자열 값이 아닌 메모리 주소를 비교(name이 포인팅하는 주소, end가 담긴 메모리의 시작주소)하게 되므로 올바르게 동작하지 않는다.
문자열 비교를 위해서는 strcmp() 함수를 사용해야 한다.
if (strcmp(name, "end") == 0) {
// 문자열이 "end"와 같음
}
포인터 배열은 동적 할당과 함께 사용되며, 동적으로 할당된 메모리 주소들을 배열로 관리하는 방식으로 활용된다.
포인터 배열을 사용할 경우, 반드시 올바른 주소로 초기화해야 한다. 초기화되지 않은 포인터는 허용되지 않은 메모리 접근(Segmentation Fault, 세그폴트)을 유발할 수 있다.
printf와 포인터 배열printf에서 %s 형식 지정자는 문자열을 출력하는데, 문자열의 주소를 넘겨야 한다.
printf("%s", names[0]);
위 코드에서 names[0]은 "hi" 문자열의 시작 주소를 가리키고 있으므로, printf는 올바르게 동작한다.
포인터 배열을 사용하면 문자열 길이에 구애받지 않고 유연한 문자열 관리가 가능하다.
앞서 사용한 포인터는 *가 하나인 단일 포인터이다. 여기서 *을 추가하면 다중 포인터(이중, 삼중 포인터 등)를 만들 수 있다.
**)이중 포인터는 포인터를 가리키는 포인터이다.
포인터의 개념:
단일 포인터: 값이 저장된 메모리 주소를 가리킨다.이중 포인터: 단일 포인터의 주소를 저장한다.int value = 10;
int *pt1 = &value; // pt1은 value의 주소를 저장
int **pt2 = &pt1; // pt2는 pt1의 주소를 저장
위 코드에서 pt2는 pt1을 가리키고, pt1은 value를 가리킨다.
메모리 관계:
value → 10
pt1 → &value (0x1000)
pt2 → &pt1 (0x2000)
이중 포인터는 포인터 변수를 함수에 전달할 때 주로 사용된다.
void allocateMemory(int **ptr) {
*ptr = (int*)malloc(sizeof(int));
}
int main() {
int *p = NULL;
allocateMemory(&p); // p의 주소를 전달하여 동적 메모리 할당
*p = 100; // 동적으로 할당된 메모리에 값 저장
free(p); // 메모리 해제
return 0;
}
위 코드에서 allocateMemory 함수는 p의 주소를 받아 malloc을 통해 메모리를 할당하고, main 함수에서 해당 메모리를 사용할 수 있도록 한다.
함수를 가리키는 포인터이다.
함수를 정의 및 선언한 후, 함수명을 호출하면 실행된다. 컴파일러는 코드를 컴파일할 때 함수명을 해당 함수의 메모리 주소로 변환하여 저장한다. 즉, 함수명은 함수 코드의 시작 주소를 의미한다.
함수는 실행 파일로 번역될 때 코드 세그먼트(Code Segment)에 위치한다. 따라서 함수의 주소는 코드 세그먼트의 특정 위치를 가리키게 된다.
함수의 주소를 포인터 변수에 저장할 수 있으며, 이를 함수 포인터라고 한다.
배열 포인터를 선언하는 방식과 유사하게, 함수 포인터도 함수의 원형을 그대로 따른다.
반환형 (*함수포인터변수)(매개변수 목록);
예제:
int add(int a, int b) {
return a + b;
}
int (*fp)(int, int); // 함수 포인터 선언
fp = add; // 함수의 주소를 함수 포인터에 저장
int result = fp(3, 4); // 함수 포인터를 사용하여 함수 호출
printf("%d", result); // 7 출력
함수 포인터도 배열로 저장할 수 있다. 동일한 반환형과 매개변수를 가지는 여러 함수를 배열 형태로 관리할 수 있다.
int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int (*operations[2])(int, int) = {add, sub};
int result = operations[0](5, 3); // add 함수 실행, 결과: 8
함수 포인터 배열을 사용하면 여러 개의 함수를 배열로 관리하여 유동적으로 선택할 수 있다.
void* 포인터는 특정 자료형을 정하지 않은 포인터이다.
void *p;
void* 포인터에는 어떤 자료형의 주소도 저장할 수 있다.void* 포인터는 연산이 불가능하다.int num = 10;
void *ptr = #
printf("%d", *(int*)ptr); // void 포인터를 int*로 변환 후 값 참조
일반적인 데이터 처리를 위해 직접 사용할 일은 많지 않지만, 라이브러리 함수에서는 void 포인터를 적극 활용한다.
예를 들어, qsort(퀵 정렬) 함수는 void 포인터를 사용하여 어떤 자료형의 배열도 정렬할 수 있도록 범용성을 제공한다.
void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void *));
이때 qsort는 데이터의 형식을 미리 알 수 없으므로, void*로 받아서 필요할 때 캐스팅하여 사용한다.
void* 포인터는 자료형이 명확하지 않으므로, 연산이 불가능하다.void *p;
char ch;
p = &ch; // 가능
// *p로 직접 참조할 수 없음 → 명시적 형 변환 필요
(char*)p; // 형 변환하여 사용 가능
즉, void*는 모든 자료형의 주소를 저장할 수 있지만, 실제 값을 사용하려면 반드시 특정 자료형으로 변환해야 한다.
실행 중에 필요한 메모리를 제공하는 기능을 동적 할당이라고 한다.
malloc() 함수메모리를 동적으로 할당하는 기본 함수이다.
void *malloc(size_t size);
size_t size: 할당받을 바이트 크기를 지정.NULL을 반환.char *p;
p = (char *)malloc(size);
malloc()은 요청한 크기만큼의 연속된 메모리 공간을 할당하고, 시작 주소를 반환한다.
malloc()을 이용한 배열 할당int *arr = (int *)malloc(10 * sizeof(int));
위 코드는 int 크기의 메모리를 10개 할당하여, arr를 배열처럼 사용할 수 있도록 한다.
realloc() 함수이미 할당된 메모리의 크기를 변경할 수 있다.
void *realloc(void *ptr, size_t new_size);
int *arr = (int *)malloc(5 * sizeof(int));
arr = (int *)realloc(arr, 10 * sizeof(int));
위 코드는 int 5개 크기의 메모리를 10개 크기로 확장한다.
주의점:
free() 함수동적 할당된 메모리를 해제하는 함수이다.
void free(void *ptr);
free() 함수는 동적 할당의 시작 주소를 전달해야 한다.int *arr = (int *)malloc(10 * sizeof(int));
free(arr);
free()를 호출하지 않으면 메모리 누수(Memory Leak)가 발생할 수 있다.
calloc() 함수동적 할당된 메모리를 0으로 초기화하는 함수이다.
void *calloc(size_t count, size_t size);
count: 할당할 요소 개수size: 각 요소의 크기calloc()은 할당된 메모리를 0으로 초기화하여 반환한다.int *arr = (int *)calloc(10, sizeof(int));
위 코드는 int 크기의 메모리를 10개 할당하고, 모든 값을 0으로 초기화한다.