1. C언어 코드 분석 중급 예제
1.1. 포인터의 크기와 길이
- 문자열 리터럴을 가리키는 문자 포인터의 동작을 이해한다.
sizeof와 strlen이 서로 다른 기준으로 값을 계산한다는 것을 확인한다.
#include <stdio.h>
#include <string.h>
int main(void) {
char* text = "0123456789";
size_t result = sizeof(text) + strlen(text);
printf("%zu\n", result);
return 0;
}
char *text는 문자열 내용을 저장하는 배열이 아니라 문자열 리터럴의 시작 주소를 저장하는 포인터이다.
sizeof(text)는 포인터 변수 자체의 크기이며, 가리키는 문자열 길이와 다르다.
strlen(text)는 포인터가 가리키는 위치부터 '\0'을 만나기 전까지의 문자 수를 센다.
- 포인터 크기는 실행 환경에 따라 달라질 수 있으며, 64비트 환경을 기준으로 설명한다.
1.2. 포인터 산술
- 배열 이름은 배열의 첫 번째 원소 주소를 나타내는 값처럼 동작하기 때문에 배열 이름을 포인터처럼 사용할 수 있다.
- 포인터에 +1을 하면 단순히 1만큼 이동하는 것이 아니라, 자료형 크기만큼 이동하여 다음 배열 원소를 가리키게 된다.
- 즉, 배열 이름은 시작 주소 역할을 하며, 포인터 연산을 하면 배열의 다음 칸으로 이동하는 것과 같은 의미이다.
#include <stdio.h>
int main(void) {
int nums[] = {4, 8, 15, 16, 23, 42};
int *p = nums + 1;
int result = *(p + 2) + p[-1];
printf("%d\n", result);
return 0;
}
nums + 1은 배열의 두 번째 원소 주소를 의미한다.
*(p + n)과 p[n]은 같은 방식으로 해석된다.
p[-1]처럼 음수 인덱스도 현재 포인터 위치를 기준으로 계산된다.
1.3. 포인터 함수 인자
- C 언어에서 함수 인자는 값에 의해 전달된다는 사실을 이해한다.
- 포인터를 인자로 전달할 경우 호출자의 변수를 변경할 수 있는 원리를 이해한다.
- 값으로 전달된 변수와 주소로 전달된 변수가 함수 호출 이후 어떻게 달라지는지 비교한다.
#include <stdio.h>
static void update(int *left, int right) {
right += *left;
*left = right - (*left / 2);
}
int main(void) {
int x = 8;
int y = 3;
update(&x, y);
printf("%d\n", x + y);
return 0;
}
&x는 변수 x의 주소이고, 함수 안에서 *left로 원본 x에 접근할 수 있다.
- 일반 정수 인자로 받은 값은 함수 내부에서 바뀌어도 호출자의 변수에는 영향을 주지 않는다.
1.4. 이중 포인터 할당
- 함수 내부에서 동적 할당한 메모리의 주소를 호출자에게 전달하는 방식을 이해한다.
char **를 사용하여 호출자의 char * 변수를 변경하는 흐름을 이해한다.
- 할당된 버퍼에 문자열을 복사하고, 사용 후 해제하는 과정을 추적한다.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static int make_copy(char **out, const char *src) {
size_t len = strlen(src);
*out = malloc(len + 1);
if (*out == NULL) {
return -1;
}
memcpy(*out, src, len + 1);
(*out)[0] = 'A';
return (int)len;
}
int main(void) {
char *buff = NULL;
int result = make_copy(&buff, "abcde");
if (result < 0) {
return 1;
}
printf("%s,%d\n", buff, result);
free(buff);
return 0;
}
&buff는 포인터 변수 buff 자체의 주소이며, char ** 매개변수로 받을 수 있다.
*out = malloc(...)은 호출자의 buff가 새로 할당된 메모리를 가리키게 한다.
strlen(src) + 1만큼 할당해야 널 문자까지 복사할 수 있다.
memcpy(..., len + 1)은 문자열 본문과 마지막 '\0'을 함께 복사한다.
(*out)[0]은 할당된 문자열의 첫 번째 문자에 접근한다.
1.5. 버퍼 크기와 문자열 누적
- 기존 문자열 뒤에 새 문자열을 덧붙일 때, 남은 버퍼 공간을 계산하는 방법을 이해한다.
- 이어 붙이기 함수가 남은 공간 범위 내에서만 복사되는 동작을 이해한다.
- 여러 번의 함수 호출이 동일한 버퍼 상태를 누적하여 변경한다는 점을 추적한다.
#include <stdio.h>
#include <string.h>
static int append_limited(char *dst, size_t dst_size, const char *src) {
size_t dst_len = strlen(dst);
size_t src_len = strlen(src);
if (dst_len + src_len >= dst_size) {
size_t room = dst_size - dst_len - 1;
memcpy(dst + dst_len, src, room);
dst[dst_size - 1] = '\0';
return -1;
}
memcpy(dst + dst_len, src, src_len + 1);
return 0;
}
int main(void) {
char buffer[10] = "abcd";
append_limited(buffer, sizeof(buffer), "efgh");
append_limited(buffer, sizeof(buffer), "ijkl");
printf("%s\n", buffer);
return 0;
}
- 문자열을 뒤에 덧붙일 때는 현재 문자열 길이와 전체 버퍼 크기를 계산하여 버퍼를 넘지 않도록 해야 한다.
- 이때 문자열의 끝을 나타내는 널 문자('\0')를 저장할 공간도 반드시 남겨야 한다.
- 문자열을 여러 번 이어붙이면 이전 결과를 기준으로 계속 누적된다.
- 그리고, 실제로 붙이는 위치는
dst + dst_len으로 표현되는 현재 문자열의 끝 지점을 가리키게 된다.