[C언어/말록랩] 연산자 우선순위에 따른 포인터 배열·배열 포인터 판독법과 힙 메모리 제어 포인터 연산 원리

자신감·2026년 10월 2일

6주차

목록 보기
1/7

1. C 언어 연산자 결합 우선순위: 배열 첨자 []와 함수 호출 ()가 역참조 *보다 먼저 결합하는 하드웨어 규칙

C 언어 문법에서 기호의 결합 우선순위는 컴파일러가 변수의 정체(타입)를 결정하는 최우선 기준입니다.

  1. 최우선 결합 기호: 배열 첨자 []와 함수 호출 소괄호 ()는 우선순위가 1등급으로 가장 높습니다. 결합 방향은 왼쪽에서 오른쪽(Left-to-Right)입니다.
  2. 후순위 결합 기호: 단항 역참조 연산자 *는 우선순위가 이보다 낮으며, 결합 방향은 오른쪽에서 왼쪽(Right-to-Left)입니다.
  3. 타입 해석의 절대 원칙:
    • 변수 식별자 이름 바로 오른쪽에 []나 ()가 붙어 있다면, 그 변수는 무조건 배열 또는 함수로 먼저 판정됩니다.
    • 변수 이름에 역참조 기호 *를 먼저 결합시켜 포인터로 선언하려면, 반드시 변수와 *를 소괄호 (*변수명)으로 감싸야 합니다.

2. 포인터 배열(int p[5])과 배열을 가리키는 포인터(int (p)[5])의 메모리 구조 및 크기(바이트) 차이

2.1 포인터 배열: int *p[5]

int *p[5];
  • 식별자 p 오른쪽에 [5]가 먼저 결합합니다. 따라서 p는 원소 5개를 담는 연속된 배열입니다.
  • 배열의 각 원소 타입은 int *, 즉 8바이트 메모리 주소입니다.
  • 물리적 메모리 크기: 64비트 시스템에서 8바이트 주소 5개가 연속 할당되므로 5×8=40바이트5 \times 8 = 40\text{바이트}를 차지합니다.

2.2 배열을 가리키는 포인터: int (*p)[5]

int (*p)[5];
  • 괄호에 의해 (*p)가 먼저 결합합니다. 따라서 p는 배열이 아니라 단 하나의 8바이트 포인터 변수입니다.
  • p가 가리키는 대상은 '정수 5개로 이루어진 int[5] 배열'입니다.
  • 물리적 메모리 크기: p 자체는 메모리 주소 1개만을 저장하므로 시스템 규격상 정확히 8바이트만 차지합니다.
  • p + 1을 실행했을 때 이동하는 메모리 보폭(Stride)은 단일 정수(4바이트)가 아니라, 가리키는 대상 전체 크기인 5×4=20바이트5 \times 4 = 20\text{바이트}만큼 물리 번지수가 증가합니다.

3. 이중 포인터 배열(int *x[5])과 포인터 배열을 가리키는 포인터(int (*x)[5])의 다차원 메모리 주소 해석법

3.1 int **x[5]

  • x에 [5]가 먼저 결합하므로 크기 5인 배열입니다.
  • 각 원소의 타입은 int **(이중 포인터)입니다.
  • 즉, 정수의 주소를 담고 있는 8바이트 포인터 변수들의 번지수 5개를 일렬로 저장하는 40바이트 메모리 블록입니다.

3.2 int (x)[5]

  • (*x)가 먼저 결합하므로 x는 단 하나의 8바이트 포인터 변수입니다.
  • x가 가리키는 대상은 '8바이트 int* 포인터 5개로 이루어진 배열'입니다.
  • 역참조 *x를 수행하면 5개의 포인터를 담고 있는 배열의 시작 주소가 나오며, x + 1 연산 시의 이동 보폭은 5×8=40바이트5 \times 8 = 40\text{바이트}입니다.

4. 배열 원소의 주소 추출(&arr[2])과 2차원 배열 전체 크기 계산 공식

4.1 &arr[2]의 물리적 타입

int arr[5] = {10, 20, 30, 40, 50};
int *p = &arr[2];
  • arr[2]는 배열의 3번째 칸에 들어있는 4바이트 정수 값(30)을 의미합니다.
  • 여기에 주소 연산자 &를 붙이면 &arr[2]는 해당 4바이트 메모리 공간의 물리 시작 번지수가 됩니다.
  • 따라서 타입은 정확히 int *가 되며, int * 타입 포인터 변수에 직접 대입할 수 있습니다.

4.2 2차원 배열 전체 메모리 바이트 계산

int matrix[2][5];
  • 물리적 크기 공식: sizeof(int)×행 개수×열 개수\text{sizeof(int)} \times \text{행 개수} \times \text{열 개수}
  • 4바이트×2×5=40바이트4\text{바이트} \times 2 \times 5 = 40\text{바이트} 연속 공간이 RAM에 할당됩니다.

5. 핀토스(Pintos) 커널 적용: 인터럽트 핸들러 테이블(intr_handlers[256])과 시스템 콜 디스패치 배열의 함수 포인터 구조

핀토스 운영체제는 하드웨어 인터럽트와 유저 시스템 콜을 처리할 때 복합 포인터 문법을 핵심 자료구조로 사용합니다.

5.1 인터럽트 핸들러 함수 포인터 배열 (threads/interrupt.c)

typedef void intr_handler_func (struct intr_frame *);
static intr_handler_func *intr_handlers[256];
  • intr_handlers는 크기 256인 포인터 배열입니다.
  • 0번부터 255번까지 각 인터럽트 벡터 번호마다 실행할 함수의 메모리 진입점 주소(8바이트)를 보관합니다.
  • CPU가 0x20(타이머 인터럽트) 번호를 보내오면, 커널은 intr_handlers[0x20](frame) 코드를 통해 조건문 분기(if-else) 없이 해당 주소로 1클럭 내에 즉시 점프하여 핸들러를 호출합니다.

5.2 시스템 콜 핸들러 디스패치 (userprog/syscall.c)

  • 유저 프로그램이 SYS_WRITE, SYS_READ 등을 호출할 때, 시스템 콜 번호를 인덱스로 사용하여 함수 포인터 테이블 syscall_handlers[syscall_num](f) 형태로 커널 진입점을 분기 실행합니다.
  • 함수 포인터 배열의 선언과 우선순위를 정확히 파악해야 커널 세그멘테이션 폴트 없이 안전한 디스패처를 구축할 수 있습니다.
profile
잘할 수밖에 없는 자신감

0개의 댓글