초보 해커를 위한 C언어와 동작 원리/Ch2. Programming

배열과 포인터

anonkorea4869 2025. 4. 22. 20:30

학습 목표

! 내용은 검토 포스트 입니다. 부정학한 내용이 있을 있으니 양해바랍니다.

  • 배열과 포인터의 관계를 알아본다.
  • printf와 scanf의 인자에 대해 이해한다.
  • OOB 취약점에 대해 이해한다.

1. 1차원 배열의 구조

아래 코드는 1Byte 자료형인 문자 자료형을 4개만큼 선언했을 때 그 주소를 출력한다. &arr[0]은 arr[0]번째의 주소를 출력한다. 하지만 arr에 주소를 출력하는 &를 출력하지 않아도 &arr[0]와 같은 주소가 출력한다. 이는 arr 그 자체로 주소이며 arr 배열의 첫번째 인덱스를 가르친다는 의미이다. 또한 &arr[0]과 &arr[1]이 1Byte 간격으로 변수가 할당 되어있음을 확인할 수 있다.

#include <stdio.h>

int main(void) {
      char arr[4];

    printf("arr = %p\n", arr); // arr = 0x16f437428
    printf("arr[0] = %p\n", &arr[0]); // &arr[0] = 0x16f437428
    printf("arr[1] = %p\n", &arr[1]); // &arr[1] = 0x16f437429
    printf("arr[2] = %p\n", &arr[2]); // &arr[2] = 0x16f43743A
    printf("arr[3] = %p\n", &arr[3]); // &arr[3] = 0x16f43743B

    return 0;
}

이번에는 4Byte 자료형인 정수 자료형을 4개만큼 선언 했을 때 그 주소를 출력한다. 이번에는 4Byte 간격으로 변수가 할당 되었음을 알 수 있다. 즉 arr[i]과 arr[i+1]의 Byte차이는 자료형의 크기만큼 차이가 남을 알 수 있다.

#include <stdio.h>

int main(void) {
    int arr[4];

    printf("arr = %p\n", arr); // arr = 0x16d9cb428
    printf("arr[0] = %p\n", &arr[0]); // &arr[0] = 0x16d9cb428
    printf("arr[1] = %p\n", &arr[1]); // &arr[1] = 0x16d9cb42C
    printf("arr[2] = %p\n", &arr[2]); // &arr[2] = 0x16d9cb430
    printf("arr[3] = %p\n", &arr[3]); // &arr[3] = 0x16d9cb434

    return 0;
}

&arr[1]의 주소와 arr + 1 같다는 것을 확인 할 수 있는데, arr + 1을 하더라도 정직하게 1을 더하는 것이 아닌 자료형의 크기만큼 곱해져서 더해지는것을 확인 할 수 있다.

#include <stdio.h>

int main(void) {
    int arr[4] = {1, 2, 3, 4};

    printf("arr = %p\n", arr); // arr = 0x16f13b420
    printf("&arr[1] = %p\n", &arr[1]); // &arr[1] = 0x16f13b424
    printf("arr + 1 = %p\n", arr + 1); // arr + 1 = 0x16f13b424

    printf("arr[1] = %d\n", arr[1]); // arr[1] = 2
    printf("*(arr + 1) = %d\n", *(arr + 1)); // *(arr + 1) = 2

    return 0;
}

2. 2차원 배열의 구조

아래 코드는 2행 2열 배열의 주소를 출력한다. arr 0번째 행과 1번째 행은 각각 1차원 배열과 같은 형태를 보인다. 2차원 배열은 1차원 배열이 행만큼 이어진 형태이기 때문이다. arr과 arr[0]은 같은 주소값을 가지는데 arr은 arr의 0번째 행의 첫번째 주소의 값을 가지고 있기 때문이다. arr은 주소값을 저장하는 변수(arr[0])의 주소값을 저장하기 때문에 더블 포인터라고 볼수 있다.

#include <stdio.h>

int main(void) {
    int arr[2][2];

    printf("arr = %p\n", arr); // arr = 0x16cfaf428

    printf("arr[0] = %p\n", arr[0]); // arr[0] = 0x16cfaf428
    printf("&arr[0][0] = %p\n", &arr[0][0]); // &arr[0][0] = 0x16cfaf428
    printf("&arr[0][1] = %p\n", &arr[0][1]); // &arr[0][1] = 0x16cfaf42c

    printf("arr[1] = %p\n", arr[1]); // arr[1] = 0x16cfaf430
    printf("&arr[1][0] = %p\n", &arr[1][0]); // &arr[1][0] = 0x16cfaf430
    printf("&arr[1][1] = %p\n", &arr[1][1]); // &arr[1][1] = 0x16cfaf434

    return 0;
}

3. OOB

OOB(Out Of Bounds)는 배열의 범위를 벗어나 접근할 수 있을때 나타나는 취약점이다. 아래 코드는 배열 4칸을 가지지만 만약 -1이하나 4이상의 인덱스에 비정상적으로 접근할 수 있다.

#include <stdio.h>

int main(void) {
    int arr[4] = {1, 2, 3, 4};

    printf("arr[-1] = %d\n", arr[-1]); // 0
    printf("arr[4] = %d\n", arr[4]); // 76463800

    return 0;
}

만약 다른 변수가 같이 선언 되었을 때 값을 다른 변수의 값을 읽을 뿐만 아니라 값을 덮어쓸 수 있다.

#include <stdio.h>

int main(void) {
    int arr1[4] = {1, 2, 3, 4};
    int arr2[4] = {11, 22, 33, 44};

    printf("arr1[4] = %d\n", arr1[-1]); // 44

    scanf("%d", &arr1[-1]); // 입력 : 0

    printf("arr2[3] = %d\n", arr2[3]);

    return 0;
}

사용자로부터 인덱스를 입력 받아 배열에 접근 할 때 인덱스의 범위를 제한할 수 있도록 해야한다.

#include <stdio.h>

int main(void) {
    int arr[4] = {1, 2, 3, 4};
    int idx;

    scanf("%d", &idx);

    if(idx < 0 || idx >= 4) {
        printf("OOB");
    } else {
        printf("%d", arr[idx]);
    }

    return 0;
}

'초보 해커를 위한 C언어와 동작 원리 > Ch2. Programming' 카테고리의 다른 글

문자열 함수  (0) 2025.04.23
함수  (0) 2025.04.23
포인터  (0) 2025.04.22
배열  (0) 2025.04.22
난수  (0) 2025.04.22