250519 정리 노트 - Hwanghyewon06/c- GitHub Wiki


1. 힙 메모리 할당 및 해제 (double 1개)

#include <stdio.h>
#include <stdlib.h>

int main()
{
    double* pk = malloc(sizeof(double));
    *pk = 100.1;
    pk[0] = 299.2;
    free(pk);
}

1) 코드 심화 설명

  • double* pk: double 타입을 가리키는 포인터 변수 선언.
  • malloc(sizeof(double)): sizeof(double)는 보통 8바이트. 힙 영역에서 8바이트 크기의 메모리를 동적으로 할당하며, 이 할당된 메모리 시작 주소를 반환.
  • 반환된 주소를 pk에 저장.
  • *pk = 100.1;pk가 가리키는 메모리 공간에 100.1(double) 값을 저장하는 코드로, pk[0]과 동일한 의미.
  • pk[0] = 299.2;*pk = 299.2; 와 같은 의미로, 이전에 저장된 100.1을 덮어쓰기함.
  • free(pk);malloc으로 할당된 메모리를 운영체제에 반환. 반환 후 pk는 유효하지 않은 메모리를 가리킴(“dangling pointer” 상태).

2) 메모리 및 동작 과정

단계 설명 메모리 상태 및 포인터 동작
1 malloc 호출, 힙(heap) 영역에서 8바이트 메모리 할당 힙에 8바이트 연속 공간 확보, 시작 주소 리턴 → pk에 저장
2 *pk = 100.1; pk가 가리키는 8바이트 공간에 100.1(double) 저장
3 pk[0] = 299.2; 이전 값 100.1이 299.2로 덮어써짐
4 free(pk); 할당된 8바이트 메모리 운영체제로 반환. pk는 여전히 주소를 가리키나 메모리는 이미 해제됨

3) 심층적 주의점 및 해석

  • malloc 반환값은 반드시 NULL 체크해야 안전 (이 예제는 생략됨).
  • *pk = 100.1pk[0] = 299.2는 동일한 메모리 위치에 값 쓰는 행위로, 두 표현 모두 pk가 가리키는 주소를 기준으로 인덱스 연산과 포인터 역참조가 동일하게 처리됨.
  • free 이후에는 pk 포인터를 다시 사용하면 안 됨 (메모리 해제 후 접근은 정의되지 않은 동작).
  • 동적 할당한 메모리는 반드시 free 해야 메모리 누수 방지.

4) 요약

  • 힙 메모리 동적 할당 → 포인터로 관리 → 값 저장 → 값 변경 → 메모리 해제
  • 포인터와 배열 인덱싱은 메모리 주소 연산으로 동일한 동작 수행
  • 메모리 해제 후 포인터는 무효(“dangling pointer”)

2. double 2개 동적 배열 할당 및 출력

#include <stdio.h>
#include <stdlib.h>

int main()
{
    double* pk = malloc(sizeof(double) * 2);
    if (pk == NULL) {
        printf("pk NULL일 수 없다.\n");
        return -1;
    }

    pk[0] = 1.1;
    pk[1] = 2.2;

    for (int i = 0; i < 2; ++i)
        printf("double: %g\n", pk[i]);

    free(pk);
}

1) 코드 심화 설명

  • malloc(sizeof(double) * 2) 호출로, double 2개 분량인 약 16바이트 메모리를 동적 할당.
  • malloc은 성공하면 해당 크기의 연속된 힙 영역 주소를 반환, 실패하면 NULL 반환 → NULL 체크 필수.
  • pk[0] = 1.1은 포인터 pk가 가리키는 시작 주소에 1.1 저장.
  • pk[1] = 2.2pk가 가리키는 시작 주소에서 sizeof(double) 만큼 떨어진 두 번째 위치에 2.2 저장.
  • for문은 i가 0부터 1까지 반복하며 pk[i]를 출력. 포인터와 배열 인덱스는 메모리 주소 계산으로 연결됨: *(pk + i)와 동일.
  • 출력 시 %g 포맷은 부동소수점 수를 과도한 0 없이 가독성 좋게 출력.
  • 마지막에 free(pk);로 동적 할당 메모리 해제.

2) 메모리 및 동작 과정

단계 설명 메모리 및 포인터 상태
1 malloc 호출, 힙에서 16바이트 연속 공간 확보 pk는 연속된 16바이트 시작 주소 저장
2 pk[0] = 1.1 : 첫 번째 8바이트에 1.1(double) 저장 힙[0~7] ← 1.1
3 pk[1] = 2.2 : 두 번째 8바이트에 2.2 저장 힙[8~15] ← 2.2
4 for문 반복하며 pk[0], pk[1] 출력 순서대로 1.1, 2.2 출력
5 free(pk)로 할당 메모리 해제 메모리 해제, pk 포인터는 무효화됨

3) 심층적 해석

  • pk[i] 표기는 컴파일러가 *(pk + i)로 바꿔서 포인터 연산 수행.
  • 힙 메모리는 연속 주소 공간이므로 배열 인덱스처럼 안전하게 접근 가능.
  • malloc 실패 시 NULL 반환하므로 반드시 확인 필요 → 프로그램 안정성 확보.
  • 출력 후 반드시 free 하여 메모리 누수 방지.

4) 요약

  • 힙 메모리를 연속된 배열처럼 사용하려면 malloc(크기 * 요소수) 형태로 할당
  • 배열 인덱스 접근은 포인터 산술과 동일
  • malloc 실패 대비 NULL 체크 필수
  • 메모리 해제 필수

3. NULL 체크 없이 double 2개 동적 배열 할당 및 출력

#include <stdio.h>
#include <stdlib.h>

int main()
{
    double* pk = malloc(sizeof(double) * 2);

    pk[0] = 1.1;
    pk[1] = 2.2;

    for (int i = 0; i < 2; ++i)
        printf("double: %g\n", pk[i]);

    free(pk);
}

1) 코드 심화 설명

  • 2번과 동일한 동작이나, mallocpk == NULL 체크가 빠짐.
  • 만약 메모리 할당에 실패하면 malloc은 NULL을 반환하지만, 이 코드는 이를 확인하지 않고 바로 포인터 역참조 수행 → 심각한 오류 가능성.
  • 이는 “안전하지 않은 코드”이며, 실제 제품이나 안정성 요구 상황에서는 절대 권장되지 않음.

2) 메모리 및 동작 과정

  • 성공 시: 2번과 동일하게 16바이트 할당, 배열 요소 출력, 메모리 해제 정상 수행.
  • 실패 시: pk == NULLpk[0] 접근 시 NULL 포인터 역참조 → 프로그램 크래시 또는 비정의 동작 발생.

3) 심층적 해석 및 주의

  • 동적 할당 시 NULL 체크를 하지 않는 것은 치명적.
  • 특히 대규모 프로그램이나 메모리 부족 상황에서는 필수 예외 처리.
  • malloc 호출 직후 반드시 NULL 여부 확인 후 처리하는 것이 올바른 코딩 습관.

4) 요약

  • NULL 체크 생략 시 프로그램 안정성 저하
  • 반드시 할당 성공 확인 후 포인터 사용 권장

4. int 4개 동적 배열 할당 및 출력

#include <stdio.h>
#include <stdlib.h>

int main()
{
    int* p = malloc(sizeof(int) * 4);
    p

[0] = 10; p[1] = 20; p[2] = 30; p[3] = 40;

for (int i = 0; i < 4; ++i)
    printf("int: %d\n", p[i]);

free(p);

}


---

### 1) 코드 심화 설명

- `malloc(sizeof(int) * 4)`로 `int` 4개 공간을 동적 할당.  
- `p[0]~p[3]`에 순차적으로 10, 20, 30, 40 할당.  
- `for`문으로 각 요소 출력.  
- `free(p);`로 메모리 해제.

---

### 2) 메모리 및 동작 과정

| 단계 | 설명                                            | 메모리 및 포인터 상태                       |
|-------|-------------------------------------------------|--------------------------------------------|
| 1     | `malloc` 호출하여 4 * 4바이트 = 16바이트 할당      | `p`는 연속된 16바이트 힙 영역 시작 주소 저장 |
| 2     | 각 인덱스에 값 저장                                | `p[0]`=10, `p[1]`=20, ...                   |
| 3     | 출력                                             | 10, 20, 30, 40 순차 출력                    |
| 4     | `free(p)`                                        | 메모리 반환, 포인터는 무효 상태               |

---

### 3) 심층적 해석

- `int` 크기는 보통 4바이트, 컴파일러 및 시스템마다 다를 수 있음.  
- 메모리 할당 크기는 반드시 `sizeof(int)`를 사용해 플랫폼 독립적 코드 작성 권장.  
- 힙 메모리는 연속 공간이므로 `p[i]` 인덱스 접근 안전.  
- `free` 호출은 동적 할당 메모리를 해제하여 메모리 누수 방지.

---

### 4) 요약

- `malloc`으로 배열 크기만큼 메모리 확보 → 값 할당 및 출력 → 해제  
- `sizeof` 연산자 사용 권장

---

# 5. int 4개 동적 배열 할당 및 초기화 (0으로)

```c
#include <stdio.h>
#include <stdlib.h>

int main()
{
    int* p = malloc(sizeof(int) * 4);
    for (int i = 0; i < 4; ++i)
        p[i] = 0;

    for (int i = 0; i < 4; ++i)
        printf("int: %d\n", p[i]);

    free(p);
}

1) 코드 심화 설명

  • malloc으로 4개 int 크기 메모리 동적 할당.
  • 할당 메모리 영역은 초기화되지 않은 상태(“쓰레기 값” 포함 가능).
  • for문으로 0으로 명시적 초기화 수행.
  • 두 번째 for문에서 초기화된 0 출력.
  • free로 해제.

2) 메모리 및 동작 과정

단계 설명 메모리 상태
1 malloc 호출, 힙에 16바이트 공간 확보 메모리 영역은 초기화 안 된 쓰레기 값 포함 가능
2 for문으로 0으로 명시적 초기화 힙[03] = 0, 힙[47] = 0, ...
3 출력 0 0 0 0 출력
4 free 호출 메모리 반환

3) 심층적 해석

  • malloc은 메모리를 초기화하지 않으므로 할당 즉시 읽으면 쓰레기 값.
  • 초기화가 필요한 경우 for문 또는 memset 함수 사용.
  • calloc 함수를 사용하면 자동 0 초기화 가능 (다음에 설명 예정).

4) 요약

  • malloc은 초기화 안 됨 → 초기화 작업 필요
  • 명시적 초기화로 안전한 메모리 상태 확보

6. calloc을 이용한 메모리 할당과 초기화

#include <stdio.h>
#include <stdlib.h>

int main()
{
    int* p = calloc(4, sizeof(int));

    for (int i = 0; i < 4; ++i)
        printf("int: %d\n", p[i]);

    free(p);
}

1) 코드 심화 설명

  • calloc(4, sizeof(int))은 4개의 int 크기만큼 메모리를 할당하면서 자동으로 0으로 초기화까지 수행.
  • malloc과 달리 추가 초기화 작업 불필요.
  • for문에서 0 출력 보장.
  • free로 메모리 해제.

2) 메모리 및 동작 과정

단계 설명 메모리 상태
1 calloc 호출, 힙에서 16바이트 메모리 할당 후 0으로 초기화 모든 바이트 0으로 설정
2 출력 0 0 0 0 출력
3 free 호출 메모리 해제

3) 심층적 해석

  • calloc은 내부적으로 malloc + memset 으로 구현.
  • 0 초기화가 필요한 배열 메모리 할당 시 편리하고 안정적.
  • 할당 실패 시 NULL 반환 가능하므로 반드시 NULL 체크 권장.

4) 요약

  • calloc = 할당 + 0 초기화
  • 메모리 초기화가 필요한 경우 권장

7. 지역 변수와 전역 변수의 차이점

#include <stdio.h>

int gVar = 10;

void f()
{
    int lVar = 20;
    printf("gVar = %d, lVar = %d\n", gVar, lVar);
}

int main()
{
    f();
}

1) 코드 심화 설명

  • gVar는 전역 변수 → 프로그램 시작 시 데이터 영역에 생성되고 프로그램 종료 시 소멸.
  • lVar는 함수 f의 지역 변수 → f 호출 시 스택에 생성되고 함수 종료 시 소멸.
  • printf는 전역 변수와 지역 변수 모두 출력 가능.
  • main에서 f 호출.

2) 메모리 및 동작 과정

변수 저장 위치 생성 시점 소멸 시점
gVar 데이터 영역 (전역) 프로그램 시작 시 프로그램 종료 시
lVar 스택 영역 (지역) f 함수 호출 시 f 함수 종료 시

3) 심층적 해석

  • 전역 변수는 프로그램 전역에서 공유 가능, 기본값 0 초기화 (초기화 명시 없으면).
  • 지역 변수는 호출마다 새로 생성 → 여러 호출 시 각각 독립적 존재 가능.
  • 전역 변수는 다른 소스 파일에서 extern 키워드로 접근 가능.
  • 지역 변수는 해당 함수 블록 내에서만 접근 가능.

4) 요약

변수 유형 저장 위치 수명 범위
전역 변수 데이터 영역 프로그램 전체 실행 기간 모든 함수에서 접근 가능
지역 변수 스택 영역 함수 호출 시 생성 및 소멸 함수 내에서만 유효

8. 지역 변수 메모리 주소 문제

#include <stdio.h>

int* GetPtr()
{
    int n = 10;
    return &n;
}

int main()
{
    int* p = GetPtr();
    printf("value = %d\n", *p);
}

1) 코드 심화 설명

  • GetPtr 함수 내 int n은 지역 변수로, 스택 메모리에 할당됨.
  • 함수 종료 후 지역 변수 n은 스택에서 사라지고 해당 주소는 더 이상 유효하지 않음.
  • GetPtr&n 주소를 반환 → main에서는 해당 주소를 역참조하지만, 이미 해제된 스택 영역 접근 → 정의되지 않은 동작 발생.

2) 메모리 및 동작 과정

단계 설명 메모리 상태 및 문제점
1 GetPtr 호출, n 스택에 생성되고 10 저장 n은 스택 프레임 내 지역 변수
2 &n 주소 반환 스택의 n 주소 반환
3 GetPtr 종료, 스택 프레임 소멸 n 메모리 해제 → 주소는 무효
4 main에서 *p 역참조 시도 무효 주소 역참조 → 비정상 동작(쓰레기 값)

3) 심층적 해석

  • 함수 내 지역 변수 주소 반환은 매우 위험.
  • 스택 프레임이 종료되면 지역 변수는 유효하지 않음.
  • 해결책: 동적 할당 메모리 사용 또는 함수 외부 변수 사용 필요.

4) 요약

  • 지역 변수 주소 반환은 스택 프레임 종료로 무효 주소 반환 → 비정의 동작 위험
  • 동적 메모리 또는 전역 변수 사용 권장

9. 배열을 함수 매개변수로 전달하여 출력

#include <stdio.h>

void PrintArray(int arr[], int size)
{
    for (int i = 0; i < size; ++i)
        printf("%d ", arr[i]);
    printf("\n");
}

int main()
{
    int data[] = {10, 20, 30, 40, 50};
    PrintArray(data, 5);
}

1) 코드 심화 설명

  • PrintArray 함수 매개변수 int arr[]는 실제로 int* arr 포인터와 동일한 의미.
  • data 배열은 크기 5로 스택에 생성됨.
  • PrintArray(data, 5) 호출 시 배열 이름 data는 첫 요소의 주소를 함수에 전달.
  • 함수 내 for문에서 arr[i]*(arr + i) 형태 포인터 연산으로 값 접근.
  • 마지막 printf("\n")로 줄 바꿈.

2) 메모리 및 동작 과정

단계 설명 메모리 및 포인터 상태
1 data

배열 스택에 5개 int 저장 | data 시작 주소를 PrintArray에 전달 | | 2 | PrintArrayarrdata 시작 주소 포인터 | arr[i]data[i]와 동일 값 참조 | | 3 | 10 20 30 40 50 순서대로 출력 | 배열 요소 정상 출력 |


3) 심층적 해석

  • 배열은 함수 인자 전달 시 복사되지 않고 주소만 전달됨.
  • 따라서 함수 내에서 배열 원본 데이터 변경 가능 (포인터 특성).
  • 함수 내에서 sizeof(arr)를 사용하면 포인터 크기만 나옴(배열 크기 아님).

4) 요약

  • 배열 이름은 배열 첫 요소 주소로 함수에 전달됨
  • 함수 내 배열 원본 데이터 접근 및 변경 가능
  • 배열 크기 별도 매개변수로 전달해야 함

10. 포인터 연산과 배열 접근 비교

#include <stdio.h>

int main()
{
    int data[] = {10, 20, 30};
    int* p = data;

    printf("%d\n", p[1]);      // 배열 인덱스
    printf("%d\n", *(p + 1));  // 포인터 연산
}

1) 코드 심화 설명

  • data 배열 초기화, pdata 첫 요소 주소 가짐.
  • p[1]*(p + 1)과 동일, 배열 인덱스와 포인터 산술은 같다.
  • 두 printf 모두 data[1] 값인 20 출력.

2) 메모리 및 동작 과정

표현식 동작 결과 값
p[1] *(p + 1)와 동일, data 두 번째 요소 참조 20
*(p+1) 포인터 p에서 1 int 요소만큼 이동해 역참조 20

3) 심층적 해석

  • 배열 인덱스는 포인터 산술 연산과 역참조 결합으로 처리됨.
  • p[i]는 컴파일러가 내부적으로 *(p + i)로 변환.
  • 포인터 연산 시 타입 크기 고려해 주소 계산.

4) 요약

  • 배열 인덱스와 포인터 산술은 동일한 메모리 접근 방식
  • 포인터 산술은 타입 크기 곱해서 주소 이동

11. 문자열 리터럴과 포인터

#include <stdio.h>

int main()
{
    char* str = "Hello";
    printf("%s\n", str);
}

1) 코드 심화 설명

  • "Hello"는 문자열 리터럴, 보통 읽기 전용 데이터 영역에 저장됨.
  • str 포인터는 이 문자열 리터럴의 시작 주소 가리킴.
  • printf("%s\n", str)은 문자열 시작 주소를 받아 끝 널 문자('\0')까지 출력.

2) 메모리 및 동작 과정

단계 설명 메모리 상태
1 문자열 리터럴 "Hello" 읽기 전용 영역에 저장 H e l l o \0 저장
2 str 포인터에 "Hello" 주소 저장 str"Hello" 시작 주소
3 printf에서 str 문자열 출력 Hello 출력

3) 심층적 해석

  • 문자열 리터럴은 불변(read-only) 데이터 영역에 저장.
  • str이 가리키는 문자열을 변경하면 정의되지 않은 동작 발생 가능.
  • 문자열 수정 필요 시 char str[] = "Hello"; 배열 선언으로 복사 생성 권장.

4) 요약

  • 문자열 리터럴은 읽기 전용 영역에 저장
  • 포인터는 문자열 시작 주소만 저장
  • 문자열 수정 불가, 수정 시 프로그램 오류 가능

12. 배열 선언과 초기화 (문자열)

#include <stdio.h>

int main()
{
    char str[] = "Hello";
    printf("%s\n", str);
}

1) 코드 심화 설명

  • char str[] = "Hello";은 문자열 리터럴을 복사해서 스택에 6바이트 배열 생성 ('H','e','l','l','o','\0').
  • str은 배열 이름으로 해당 메모리 시작 주소.
  • printf로 문자열 출력.
  • 이 배열은 수정 가능.

2) 메모리 및 동작 과정

단계 설명 메모리 상태
1 스택에 6바이트 크기 배열 생성 'H','e','l','l','o','\0' 저장
2 str 배열 이름은 시작 주소 str → 배열 첫 요소 주소
3 printf에서 문자열 출력 Hello 출력

3) 심층적 해석

  • char str[] = "Hello";는 리터럴을 복사해 배열 생성 → 메모리 수정 가능.
  • 문자열 수정 시도 가능 (예: str[0] = 'h';)
  • str은 배열 이름 → 상수 포인터 역할, 배열 크기 변경 불가.

4) 요약

  • 문자열 리터럴을 배열로 복사해 스택에 생성 → 수정 가능
  • char* 포인터와 달리 메모리 위치 고정 및 안전

13. 문자열 포인터 수정 시도 (위험)

#include <stdio.h>

int main()
{
    char* str = "Hello";
    str[0] = 'h';  // 위험한 코드
    printf("%s\n", str);
}

1) 코드 심화 설명

  • str이 가리키는 문자열 리터럴은 읽기 전용 영역.
  • str[0] = 'h';로 수정 시도 → 읽기 전용 메모리 수정 시도 → 정의되지 않은 동작, 프로그램 크래시 가능.
  • 안전한 방법은 char str[] = "Hello";로 복사 생성 후 수정.

2) 메모리 및 동작 과정

단계 설명 메모리 상태
1 문자열 리터럴 "Hello" 읽기 전용 영역에 저장 H e l l o \0
2 str 포인터가 리터럴 주소 가짐 읽기 전용 메모리
3 str[0] = 'h' 실행 시도 읽기 전용 메모리 수정 시도 → 크래시 가능

3) 심층적 해석

  • 문자열 리터럴은 변경 불가 데이터
  • 읽기 전용 메모리 수정 시도는 비정의 동작
  • 보안 취약점 및 프로그램 비정상 종료 위험

4) 요약

  • 문자열 리터럴 포인터를 통한 수정 시도 위험
  • 반드시 배열 복사 후 수정 권장

14. 동적 할당 후 문자열 복사 및 수정

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
    char* str = malloc(6);
    strcpy(str, "Hello");
    str[0] = 'h';
    printf("%s\n", str);
    free(str);
}

1) 코드 심화 설명

  • malloc(6)으로 6바이트 메모리 동적 할당.
  • strcpy(str, "Hello")로 문자열 복사 (널 종료 문자 포함).
  • str[0] = 'h'로 첫 문자를 소문자 'h'로 수정 가능 (힙 메모리).
  • printf로 출력 → hello 출력.
  • 마지막 free(str)로 메모리 반환.

2) 메모리 및 동작 과정

단계 설명 메모리 상태
1 힙 영역에 6바이트 메모리 할당 빈 공간 확보
2 "Hello" 문자열 복사 H e l l o \0 저장
3 str[0] = 'h' 문자 변경 h e l l o \0로 변경
4 문자열 출력 hello 출력
5 free 호출 할당 메모리 반환

3) 심층적 해석

  • 동적 할당 메모리는 쓰기 가능.
  • 문자열 리터럴과 다르게 안전하게 수정 가능.
  • 메모리 할당 실패 시 malloc NULL 체크 권장.

4) 요약

  • 동적 할당 메모리에 문자열 복사 → 안전한 수정 가능
  • 메모리 해제 필수

15. 동적 할당과 NULL 체크, 복사 및 수정

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
    char* str = malloc(6);
    if (str == NULL)
    {
        printf("메모리 할당 실패\n");
        return -1;
    }

    strcpy(str, "Hello");
    str[0] = 'h';
    printf("%s\n", str);
    free(str);
}

1) 코드 심화 설명

  • 14번 코드와 같으나 malloc 실패 시 NULL 체크 추가됨.
  • 실패 시 에러 메시지 출력 후 프로그램 종료.
  • 성공 시 문자열 복사 및 수정 수행.
  • 안정성 향상.

2) 메모리 및 동작 과정

단계 설명 메모리 상태 및 동작
1 malloc 호출, 메모리 할당 시도 성공하면 힙 메모리 할당, 실패하면 NULL 반환
2 NULL 체크 수행, 실패 시 메시지 출력 및 종료 실패 시 더 이상 진행하지 않고 종료
3 strcpy 및 문자열 수정 수행 정상 메모리 영역에 복사 및 수정
4 출력 및 free 호출 문자열 출력 및 메모리 해제

3) 심층적 해석

  • malloc 실패 대비 코드 작성은 프로그램 견고성 증가.
  • 동적 할당 메모리 관리의 필수 안전 점검 패턴.
  • 메모리 누수 및 크래시 예방.

4) 요약

  • 동적 할당 시 반드시 NULL 체크 필수
  • 오류 처리 및 안전한 메모리 관리 중요

전체 요약 정리표

| 번호 | 주제            | 핵심 내용             | 메모리 영역 | 주의점 |
| -- | ------------- | ----------------- | ------ | --- |
| 1  | 힙 메모리 할당 및 해제 | `malloc`으로 double |        |     |


2개 크기 할당 후 사용 및 해제 | 힙 (동적 메모리)  | 할당 크기 정확히, `free` 호출 필수       |
| 2     | 초기화 없는 `malloc`         | 초기화 안 됨, 쓰레기 값 가능                     | 힙               | 초기화 필요 시 직접 수행 또는 `calloc` 권장 |
| 3     | `calloc` 사용                | 0으로 초기화 된 메모리 할당                       | 힙               | NULL 체크 필수                           |
| 4     | 지역 변수 vs 전역 변수        | 지역 변수는 함수 호출 시 생성, 전역 변수는 프로그램 전체 | 스택 / 데이터 영역 | 전역 변수는 모든 함수 접근 가능           |
| 5     | 지역 변수 주소 반환 문제       | 반환 후 주소 무효화, 정의되지 않은 동작 발생       | 스택              | 지역 변수 주소 반환 금지                  |
| 6     | 배열 함수 전달                | 배열 이름은 주소, 함수에서 배열 크기 필요         | 스택              | 배열 크기 별도 전달                       |
| 7     | 포인터 연산 vs 배열 인덱스    | 동일 동작, 포인터 산술로 주소 계산                  | 메모리            | 타입 크기 고려                            |
| 8     | 문자열 리터럴과 포인터       | 읽기 전용 영역, 수정 불가                         | 읽기 전용 영역      | 수정 시 크래시 위험                       |
| 9     | 문자열 배열 선언             | 리터럴 복사 배열 생성, 수정 가능                    | 스택              | 수정 가능                                |
| 10    | 동적 할당 문자열 복사 및 수정 | 힙 메모리 할당, 수정 가능                         | 힙               | `malloc` 후 NULL 체크 및 `free` 필수     |

⚠️ **GitHub.com Fallback** ⚠️