Programmers

[40일차] C언어와 JavaScript의 메모리 관리 및 포인터

PARKpatchnotes 2025. 11. 5. 17:59

1. 포인터(Pointer)란 무엇인가? (C언어)

컴퓨터의 메모리는 데이터가 저장되는 수많은 ‘번지수’로 구성되어 있습니다. 포인터란, 일반적인 데이터(값) 대신 이 메모리의 번지수(주소)를 값으로 저장하는 특별한 변수입니다.

즉, 포인터는 데이터가 어디에 있는지를 가리키는 ‘지시자’ 역할을 합니다. 이를 통해 데이터에 직접 접근하는 대신, 데이터의 위치를 참조하여 간접적으로 값을 읽거나 수정할 수 있습니다.

C언어에서 포인터를 다루기 위해 다음과 같은 핵심 연산자가 사용됩니다.

  • 주소 연산자 (&): 변수 앞에 붙여 해당 변수의 메모리 주소값을 가져옵니다.
  • 역참조 연산자 (*): 포인터 변수 앞에 붙여, 포인터가 가리키는 주소에 저장된 실제 데이터 값에 접근합니다.

포인터 예시 코드

#include <stdio.h>

int main() {
    int value = 100;       // 1. int형 변수 'value'에 100을 저장
    int *ptr = &value;     // 2. 포인터 'ptr'에 'value'의 메모리 주소를 저장

    // 변수와 포인터의 값 출력
    printf("value의 실제 값: %d\n", value); // 100
    printf("value의 메모리 주소 (&value): %p\n", &value); // 주소 값
    printf("ptr에 저장된 값 (주소): %p\n", ptr); // 주소 값
    printf("ptr이 가리키는 주소의 실제 값 (*ptr): %d\n", *ptr); // 실제 값

    // 역참조를 통해 원본 데이터 수정
    *ptr = 250; // 포인터로 value의 값을 변경
    printf("포인터로 변경 후 value의 값: %d\n", value); // 250

    return 0;
}

설명:
위 코드에서 ptrvalue의 값을 직접 가지지 않고, value가 저장된 메모리의 위치(주소)를 값으로 가집니다. *ptr 연산을 통해 그 위치에 접근하여 값을 읽거나 수정할 수 있습니다.

포인터는 동적 메모리 할당, 배열의 효율적인 관리, 그리고 함수에 큰 데이터 구조를 복사 없이 전달하는 등 C 프로그래밍의 핵심적인 작업을 수행하는 데 필수적입니다.


2. 핵심 분석: 일반 변수와 포인터 변수의 비유

가. 일반 변수: 값이 저장된 ‘집’ 🏠

일반 변수는 실제 데이터(값)를 담는 공간입니다.

  • 변수 이름(a): 집에 붙여진 별칭 (예: '길동이네 집')
  • 메모리 주소(&a): 집의 실제 위치 ("C언어 마을 100번지")
  • 변수의 값('홍길동'): 집에 사는 사람 또는 물건 ('홍길동')

a = '박대감'의 의미: 집을 그대로 둔 채, 집 안에 살고 있는 사람만 바꾸는 것. 주소는 변하지 않고 내용물만 교체됩니다.

나. 포인터 변수: 주소를 저장하는 ‘지도’ 또는 ‘주소록’ 🗺️

포인터 변수는 다른 변수의 위치(주소)를 저장하는 변수입니다.

  • 포인터 변수(char *ptr): 다른 집의 주소가 적힌 '지도'나 '주소록'
  • 포인터의 값(ptr): 지도에 적힌 실제 번지수 ("C언어 마을 100번지")
  • 역참조(*ptr): 주소록을 보고 해당 위치에 가서 직접 값을 읽거나 수정하는 행동

ptr = &b의 의미: 지도에 적힌 주소를 수정하여 새로운 장소를 가리키도록 변경합니다.


3. 포인터의 힘과 위험성

가. 일반 변수: 안전한 ‘우체국’ 시스템 📬

운영체제(OS)는 변수 이름으로 접근하고, 변수의 메모리 경계를 안전하게 보호합니다. 따라서 다른 변수의 메모리 영역을 침범할 가능성이 없습니다.

나. 포인터 변수: 통제 불능의 ‘사채꾼’ 또는 ‘강제집행’ 🔪

포인터는 변수의 메모리 주소를 알고 있기 때문에, 시스템 통제 없이 메모리에 직접 접근할 수 있습니다.

포인터를 잘못 사용하면 다른 변수 또는 시스템 메모리에 접근해 프로그램 오류(Crash)를 일으킬 수 있습니다.
따라서 매우 강력하고 위험한 도구이므로 신중히 사용해야 합니다.


4. JavaScript와 포인터의 연관성: 참조(Reference)

JavaScript는 C언어처럼 개발자가 직접 메모리 주소를 다루는 포인터가 존재하지 않습니다. 대신, 참조(Reference)라는 개념을 통해 유사한 방식으로 동작합니다.

JavaScript는 데이터를 크게 두 종류로 나눕니다.

  1. 원시 타입 (Primitive Types): string, number, boolean, null, undefined

    • 변수에 값(Value) 자체가 저장됩니다. C언어의 일반 변수와 유사합니다.
    • let a = 10; let b = a;를 실행하면, b에는 a의 값 10복사되어 저장됩니다. ab는 서로 독립적입니다.
  2. 객체 타입 (Object Types): Object, Array, Function

    • 변수에는 데이터가 저장된 메모리 주소(참조)가 저장됩니다. 이는 C언어의 포인터와 매우 유사한 원리입니다.
    • let obj1 = { a: 10 }; let obj2 = obj1;을 실행하면, obj2에는 obj1이 가리키는 객체의 주소복사됩니다.
    • 결과적으로 obj1obj2동일한 객체를 가리키게 되므로, obj2.a = 20;을 실행하면 obj1.a의 값도 20으로 변경됩니다.

JavaScript 예시 코드

// 원시 타입: 값에 의한 전달 (Call by Value)
let num1 = 10;
let num2 = num1; // num1의 '값'이 복사됨
num2 = 20;

console.log(num1); // 10 (num2의 변경이 num1에 영향을 주지 않음)
console.log(num2); // 20

// 객체 타입: 참조에 의한 전달 (Call by Reference)
let user1 = { name: '홍길동' };
let user2 = user1; // user1의 '주소(참조)'가 복사됨
user2.name = '박대감';

console.log(user1.name); // '박대감' (user2의 변경이 user1에도 영향을 줌)
console.log(user2.name); // '박대감'

결론적으로, JavaScript는 포인터의 복잡성(메모리 직접 제어, 수동 해제)을 숨기고 가비지 컬렉터(Garbage Collector)가 메모리를 자동으로 관리하는, 더 안전하고 추상화된 모델을 사용합니다. 하지만 객체를 다룰 때는 C언어의 포인터처럼 '주소'를 통해 데이터를 공유한다는 근본 원리를 이해하는 것이 중요합니다.


5. 결론

C언어와 JavaScript는 메모리 관리에 있어 다른 접근 방식을 취하지만, 데이터 처리의 근본 원리는 맞닿아 있습니다.

  • C언어포인터를 통해 개발자에게 메모리에 대한 직접적이고 강력한 제어권을 부여합니다. 이는 높은 성능 최적화를 가능하게 하지만, 메모리 누수나 충돌과 같은 위험성을 내포합니다.
  • JavaScript참조가비지 컬렉션을 통해 포인터의 복잡성을 추상화하고 자동화된 메모리 관리를 제공합니다. 이는 개발 편의성과 안정성을 높여주지만, 객체 데이터가 '주소'를 통해 공유된다는 점을 이해하지 못하면 예기치 않은 동작을 만날 수 있습니다.

두 언어의 차이를 이해하는 것은 특정 작업에 더 적합한 도구를 선택하고, 메모리 동작 원리를 깊이 있게 파악하는 데 큰 도움이 됩니다.