C언어를 포기하지 않고 동적 메모리 할당까지 왔다면 이미 컴퓨터의 원리에 대하여 상당한 수준의 이해를 하고 있는 것이다.  대부분은 포인터에서 포기하고 좀더 쉬운 언어들을 찾을 수도 있다.

 

사실 프로그래밍 언어에 다른 쉬운 언어란 것은 없다. 자바스크립트와 HTML/CSS 를 담당하는 웹개발자가 공부해야 할 양은 상상을 초월한다.

 

Mozilla 유튜브 채널에서 말하는 웹기술의 구현은 AAA급 게임에 맞먹는 컴퓨팅 기술이 필요하다고 한다. 서버와 네트워크 기능이 분리 되어서 클라이언트인 사용자의 PC에서는 쉽게 접근할 수 있는 것 처럼 느껴질 뿐이다. HTML을 조작하면 무언가 보이는 것을 쉽게 바꿀 수 있기 때문에 그렇게 느껴진다.

HTML MEME도 그런 이유로 발달했는지도...

 

Javascipt Mozilla

 

 

동적 메모리 할당에 대해 실습하기 전에 프로세스가 사용하는 메모리 구조에 대하여 리마인드 할 필요가 있다.

 

C프로그램(실행 중 프로세스)이 실행되는 시점과 환경을 Runtime Environment 라고한다. 컴파일 타임은 소스코드를 기계어로 번역하며 검증하는 시간이다. 런타임이 되면 아래의 메모리 구조를 사용한다.

 

1. 코드 세그먼트

- 코드의 영역이다. 기계어 명령문이 기록되어 있다. 명령문은 CPU가 작업할 특정한 행동의 내용이다.

 

2. 데이터 세그먼트

- 데이터 세그먼트 지만 모든 데이터가 들어가지 않는다. 프로그램의 시작부터 끝까지 사용해야 할 전역변수, static 전역 변수,문자열 상수 들이 포함된다.

 

*메모리 세그먼트에 대해서는 별도의 포스팅을 할 예정이지만 한가지 예제만 보고 간다. 문자열 상수는 데이터 세그먼트에 저장된다. 소스코드 작성시 "" 안의 문자열 형식을 사용하면 스택에 저장되는 것이 아니라 데이터 세그먼트 영역으로 보내진다. 포인터로 추적하면 데이터 세그먼트 영역에 위치한다. 포인터를 두개 선언하더라도 하나의 주소를 가리키고 있다. 컴파일러가 동일한 문자열 상수를 두개 만들지 않기 때문이다.

 

또 기존의 포인터에 새로운 문자열을 할당하면 데이터 세그먼트 영역에 새로운 문자열 상수의 위치를 대입한다.

 

아래에서 보면 40의 주소들은 데이터 세그먼트 영역이고 60의 주소는 스택 영역임을 알 수 있다.

 

* 데이터 세그먼트 영역의 문자열은 포인터로 접근이 가능하나 값을 변경할 수 없다.

#include <stdio.h>

void main(){
    char *ptr1 = "Good day my lord!";
    char *ptr2 = "Good day my lord!";
    char str1[] = "Good day my lord!";
    char* ptr3 = str1;

    printf("%s : %p\n",ptr1, ptr1);
    printf("%s : %p\n",ptr2,ptr2);
    printf("%s : %p\n",str1,str1);

    ptr2 = "Hello World, sir!";
    printf("%s : %p\n",ptr2,ptr2);

    *(ptr3+1) = 'u';
    *(ptr3+2) = 'u';
    printf("%s : %p\n",ptr3,ptr3);
}

이에 반해 배열로 복사한 것은 (str1) 스택세그먼트의 스택영역에 위치한다. 같은 문자열이라도 내부적으로 위치한 구조가 다르다.

 

스택은 고정된 길이지만 값을 변경할 수 있다. 포인터로도 당연히 변경할 수 있다. [Guud day]

 

3. 스택 세그먼트

- 정적 메모리 스택에는 값과 크기가 정해진 지역변수가 저장된다. 지역변수의 사용이 끝날 때 마다 자동으로 메모리가 해제되어 데이터는 소멸된다.

 

- 동적 메모리 힙에는 가변길이 변수를 생성할 수 있다. 프로그래머가 직접 메모리 해제를 하지 않는 한 프로세스가 끝날 때까지 메모리에 공간을 차지한다.

 

메모리 구조를 이해하려면 개별 동작 방식을 하나씩 들여다 봐야한다. 이 포스팅에서 주목해야 할 부분은 스택 세그먼트이다. 동적 메모리 할당은 스택 세그먼트의 Stack 영역과 Heap 영역 중에서 HEAP 힙 영역에 할당하는 것이다.

 

스택은 애초에 크기가 정해져 있어서 컴파일 타임에 배치가 정해진다. 스택의 크기를 바꾸려면 컴파일을 다시해야 한다. 

 

비주얼 스튜디오의 경우 스택의 기본값으 1메가 이다.

 

 

반면 히프 영역은 런타임에 가변적으로 변경이 가능하다. 스택 처럼 메모리의 제한이 없다. 물론 그렇다고 OS 메모리 제한을 넘을 수 없고, 자원을 효율적으로 사용할 수 있는 코드를 작성해야 한다.

 

동적 메모리 할당 예제

먼저 기본 할당 문법은 아래와 같다. malloc 함수는 stdlib.h 가 필요하다.

 

정수형 포인터를 선언하면서 malloc 함수는 int * 로 형변환을 하고 있다. 이것은 함수의 원형이 void * malloc(size_t size) 형이라 void 포인터를 반환하기 때문이다. void형인 이유는 메모리 할당이 char형인지 int 형인지 정해져있지 않고 함수를 호출할 때 알 수 있기 때문이다. 입력되는 인수는 바이크의 크기이다.

 

바이트의 크기가 100이면 4바이트 int 형은 25개 공간으로 사용할 수 있다. 배열 25개를 쓰는 것과 비슷하다.

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

void main(){
    
    int *ptr = (int *)malloc(100);
    *(ptr+0) = 15;
    *(ptr+1) = 30;
    *(ptr+2) = 45;
    *(ptr+3) = 60;
    *(ptr+4) = 75;

    for(int i=0; i<5; i++){
        printf("value: %d , address: %p\n",*(ptr+i),(ptr+i));
    }
}

GCC 컴파일러의 주소 번지수도 달라졌다. 위의 데이터세그먼트 예제에서 40 대가 데이터세그먼트, 스택영역은 60 대 힙 영역은 72대를 보여주고 있다. 메모리의 번지수는 시스템과 실행시의 환경에 따라 달라지지만 비슷한 환경에서 테스트를 하면 어느정도 일정한 구역이 나눠져 있음을 볼 수 있다.

 

지금은 예제 프로그램이니까 메모리 해제를 하지 않았다. 허나 사용이 끝난 동적 메모리는 반드시 직접 해제시켜줘야한다. 안그러면 프로세스의 끝날 때까지 메모리를 점유하게 된다.

 

다음 예제는 사용자에게 수를 입력받은 만큼 동적 메모리를 할당하여 짝수의 합계를 구하는 코드이다.

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

void main(){
    
    int num1;
    int *ptr1;
    int sum;

    printf("Insert a number to sum up : \n");
    scanf("%d",&num1);
    ptr1 = (int *)malloc(sizeof(int)*num1);

    for (int i = 1; i <= num1; i++){
        ptr1[i] = i*2;
        sum += ptr1[i]; 
    }

    for (int i = 1; i <= num1; i++){
        printf("ptr1[%3d] : %3d, address : 0x%08X\n", i, *(ptr1+i), (ptr1+i));   
    }
        printf("sum : %d\n",sum);
    free(ptr1);
}

입력받은 숫자 값만큼 정수형 동적 메모리를 할당하고 각 공간에 순차적으로 짝수의 값을 저장한다. 마지막에 free 로 할당된 메모리를 해제하는 것을 잊지 않는다.

 

여기서 핵심은 사용자에게 입력받은 값만큼 메모리를 할당할 수 있다는 것이다.

 

1만을 누산하는데 A40094 에서 A49CC0 까지 사용했다. CMD 화면 버퍼가 9999가 한계여서 밀렸다.

 

1부터 1만까지 4바이트 int형을 메모리를 할당하기 위해 156 키로 바이트가 할당된다. 1메가의 스택이라면 큰 데이터지만 8기가 단위의 요즘 메모리 용량으로 보면 히프 HEAP 영역에서는 아주 작은 크기다.

예전의 램은 정말 작았다. 지금과 비교해서 보면. 286의 램이 1메가 정도 였고 지금의 스택크기밖에 되지 않는다. 거기엔 DOS운영체제(윈도우 이전)의 커널도 올라가야 하고 프로그램 하나만을 위해 사용할 수 없었다. (90년대)

 

과거의 기사. 286의 램은 1메가 정도 였다.

 

[컴퓨터]돌아본 PC 10년史-10년전엔 286도 "꿈의 컴퓨터"

‘우리가 기다렸던 꿈의 컴퓨터 286AT. 1MB의 충분한 메모리용량과 무려 40MB의 하드디스크….’ 12㎒ 80286 CPU

www.donga.com

 

C가 처음 개발되기 시작한 70년대의 컴퓨팅 환경을 생각하면 메모리 구조에 대한 이해와 효율성을 달성하는 것이 얼마나 중요했는지 이해가 간다.

 

항상 인간의 발전은 한계를 돌파하는데 있었다. 여전히 하드웨어의 한계를 돌파하는데 C는 중요한 역할을 하고 있다. 아무리 하드웨어가 100배 1000배 발전해도 거기엔 또 새로운 한계가 설정될 뿐이다.

 

50년이나 된 C를 배우는 것은 그 한계에 대한 깊이를 이해하는데 도움이 될 것이라 생각한다.

그렇지 않으면 이것을 배우는데 쏟는 시간들은 너무나도 허무하다. 그 시간에 웹사이트와 안드로이드 어플을 개발한다...

 

세그먼트 구조에 대하여는 좀더 자료를 모아서 정리해 둬야겠다. 구조를 모르고 C를 배우면 웬만한 사람은 중도에 포기하는 구조가 되버리는 것이 어쩌면 순서의 문제일지도 모른다.

공유하기

facebook twitter kakaoTalk kakaostory naver band