1619, 1/81 회원가입  로그인  
   sihun1113
   C언어3-메모리와 변수

http://www.hackerschool.org/HS_Boards/zboard.php?AllArticle=true&no=1736 [복사]


*----------------------------------------------------------------------------------------------------------*

이 카테고리는 그동안 배웠던것이라던가 공부하면서 알게된 내용들을 중심으로 혹여 참고가 될 수 있을까 싶은 내용들을

모아둔 곳입니다. 따라서 이 글들을 대상이 되는 분들은.. 프로그래밍을 갓 시작했거나 개념이 마구마구 흔들리시는 분들

정도가 되겠습니다. 그러니 너무 욕하지는 말아주세요 =_=;;;



혹시 제가 잘못 알고 있는 부분들이라던가 궁금한 것이 있으면 쪽지 남겨주시거나 리플을 달아주시면 감사하겠습니다.

*----------------------------------------------------------------------------------------------------------*



※ 여기에 쓰인 설명에 대해서 어렵다고 하시는 분들이 많이 계시네요. 아마도.. 뜬금없이 메모리 할당이니 뭐니 하는 얘기가 나와서 그런것이라 생각돼요. 제가 부족한 탓에... 오히려 혼동만 일으키는 건 아닌지 걱정입니다 =ㅁ=;;;

조금이나마 도움이 될까 해서 새로 '[03 - 메모리와 변수]편의 이해를 돕기 위한 팁...' 이라는 글을 올려두었습니다.

팁...이라기보다 거의 새 강좌가 되어버렸지만 말이죠 ㅡ,.ㅡ;; 그걸 참조해 주세요.

--------------------------------------------------------------------------------------------------------------





여러분은 아마도 수없이 많은 변수들을 선언하고 사용해 보셨을겁니다. 그런데 이 변수라는 녀석들이 어디에 생겨서 어떻게 사용되는지 생각해 보셨나요? 조금 들어보신 분들이시라면 전역변수는 '힙'메모리에 로컬변수는 '스택'메모리에라는 정도는 들어보셨을겁니다.



제가 여기서 설명하려는 건 힙이나 스택같은 구조적인 부분을 설명하려는것이 아니고 메모리 공간 안에서 변수가 어떻게 자리를 차지하고 어떻게 불러지는지만 보여드리려는겁니다. 힙이 됐건 스택이 됐건 어디까지나 메모리 안에서 결정되는 것이니까요. 그리고 아무리 입이 아프게 변수가 어쩌고저쩌고 설명하는 것 보다 메모리와의 연계를 설명하는 것이 변수 이해에 있어서 최고의 방법이라고 생각합니다. 저도 이 메모리와 관계된 내용을 접했을때에야 비로소 포인터라는 녀석도 이해를 하게 됐거든요.





Application이 OS로부터 할당받은  128 Byte의 기억 공간이 주어져있다고 생각해 봅시다. 그 공간은 아마도 메모리 상에서 다음과 같은 모양(?)으로 정의 되어있을겁니다.



(□ 한칸은 1Byte, 초기화되지 않은 상태로 무슨 값이 들어가 있는지는 알수 없음)

       0 1 2 3  4 5 6 7  8 9 a b  c d e f  0 1 2 3  4 5 6 7  8 9 a b  c d e f

0x0- □□□□ □□□□ □□□□ □□□□ □□□□ □□□□ □□□□ □□□□

0x2- □□□□ □□□□ □□□□ □□□□ □□□□ □□□□ □□□□ □□□□

0x4- □□□□ □□□□ □□□□ □□□□ □□□□ □□□□ □□□□ □□□□

0x6- □□□□ □□□□ □□□□ □□□□ □□□□ □□□□ □□□□ □□□□



(실제로 저렇게 생겼다는건 아니지만 편의상 4Byte단위로 구분했습니다. 그나마 보기 쉽죠? ㅎㅎ..)



이 공간에 integer형 변수 하나를 선언한다고 하면 어떻게 할당이 될까요?





int a;



       0 1 2 3  4 5 6 7  8 9 a b  c d e f  0 1 2 3  4 5 6 7  8 9 a b  c d e f

0x0- □□□□ □□□□ □□□□ □□□□ □□□□ □□□□ □□□□ □□□□

0x2- □□□□ □□□□ □□□□ □□□□ □□□□ □□□□ □□□□ □□□□

0x4- □□□□ □□□□ □□□□ □□□□ □□□□ □□□□ □□□□ □□□□

0x6- □□□□ □□□□ □□□□ □□□□ □□□□ □□□□ □□□□ □□□□



또 하나의 변수를 선언하면



int iNumA;

int iNumB;



       0 1 2 3  4 5 6 7  8 9 a b  c d e f  0 1 2 3  4 5 6 7  8 9 a b  c d e f

0x0- □□□□ □□□□ □□□□ □□□□ □□□□ □□□□ □□□□ □□□□

0x2- □□□□ □□□□ □□□□ □□□□ □□□□ □□□□ □□□□ □□□□

0x4- □□□□ □□□□ □□□□ □□□□ □□□□ □□□□ □□□□ □□□□

0x6- □□□□ □□□□ □□□□ □□□□ □□□□ □□□□ □□□□ □□□□





그리고 이 변수들이 각각 반환하는 주소는 할당받은 각 메모리의 첫주소인 iNumA:0x00, iNumB:0x04 를 반환합니다.

여기에 값을 대입하면(역시 Big-endian이냐 Little-endian이냐에 따라 다르지만 여기까지는 큰 의미가 없기때문에 편의상 보기 쉬운 Big-endian방식으로 설명합니다.참고로 윈도우즈는 Little-endian 방식이므로 거꾸로 저장됩니다.)



iNumA = 1;

iNumB = 10;



       0 1 2 3  4 5 6 7  8 9 a b  c d e f  0 1 2 3  4 5 6 7  8 9 a b  c d e f

0x0-  0 0 0 1  0 0 0 a □□□□ □□□□ □□□□ □□□□ □□□□ □□□□

0x2- □□□□ □□□□ □□□□ □□□□ □□□□ □□□□ □□□□ □□□□

0x4- □□□□ □□□□ □□□□ □□□□ □□□□ □□□□ □□□□ □□□□

0x6- □□□□ □□□□ □□□□ □□□□ □□□□ □□□□ □□□□ □□□□





이런식으로 세팅이 됩니다.

(참고로, Little-endian에서는 iNumA는 1000, iNumB는 a000 이런식으로 저장됩니다. 이 차이점은 별도로 검색해보세요.)



앞서 □는 값을 알수 없다고 했습니다. 따라서 만약 iNumA=1과 같은 초기화 과정을 거치지 않는다면 이 변수를 사용할때 알수없는 값이 사용될 가능성이 있습니다. 이 때 이 '알수없는 값'을 쓰레기값(garbage value)이라고 부릅니다. 이 쓰레기값은 프로그램의 이상작동의 원인이 되기도 하고 때로는 데이터를 잃게만드는 등 치명적인 결과를 초래하게 될 수도 있기때문에 저는 '반드시 초기화하라'라고 권하고 싶습니다. 아무리 사소한 변수라도 말이죠.

지금은 귀찮다고 생각할지 몰라도 습관을 들여놓지 않으면, 후에 수천, 수만라인에 이르는 코드를 작성하게 될때 반드시 후회하게 되는 날이 오게됩니다.





자, 그렇다면 배열은 어떻게 저장 될 것인가? 배열도 마찬가지 입니다.

만약 4개짜리 정수형 배열을 선언했다고 하면 그 배열은 다음과 같이 할당됩니다.



int Ary[6];



       0 1 2 3  4 5 6 7  8 9 a b  c d e f  0 1 2 3  4 5 6 7  8 9 a b  c d e f

0x0- □□□□ □□□□ □□□□ □□□□ □□□□ □□□□ □□□□ □□□□

0x2- □□□□ □□□□ □□□□ □□□□ □□□□ □□□□ □□□□ □□□□

0x4- □□□□ □□□□ □□□□ □□□□ □□□□ □□□□ □□□□ □□□□

0x6- □□□□ □□□□ □□□□ □□□□ □□□□ □□□□ □□□□ □□□□

       Ary[0](주소:0x00), Ary[1](주소:0x04), Ary[2](주소:0x08),

       Ary[3](주소:0x0c), Ary[4](주소:0x10), Ary[5](주소:0x14)



참고로 실제 제 컴퓨터 상에서 변수를 선언하고 할당받은 주소를 얻은 결과입니다.

코드는 단순히 주소만을 반환하는 것으로 간단히 짜봤습니다.

*- 코드 --------------------------------------------------------------------------------*

#include <stdio.h>
#include <stdlib.h>
#include <conio.h>   // getch()함수를 사용하기 위해 포함시킨 헤더



int main()
{
    int   a[5];            // 5개의 정수형 변수배열을 선언



    printf( "[%d|%x]\n[%d|%x]\n[%d|%x]\n[%d|%x]\n[%d|%x]",
             &a[0], &a[0], &a[1], &a[1], &a[2], &a[2],
             &a[3], &a[3], &a[4], &a[4]);



    getch();             // 결과를 확인하기 위해 아무키나 입력 받기 전까지 대기한다.
    return 0;
}

*--------------------------------------------------------------------------------------*

*- 결과 --------------------------------------------------------------------------------*

[4358076|427fbc]

[4358080|427fc0]

[4358084|427fc4]

[4358088|427fc8]

[4358092|427fcc]

*--------------------------------------------------------------------------------------*





그러면 long이나 double 형의 변수 할당은 어떻게 될까요?

다를건 없습니다. 할당되는 길이만 달라질 뿐이죠.



long    lVar;

double dVar;



       0 1 2 3  4 5 6 7  8 9 a b  c d e f  0 1 2 3  4 5 6 7  8 9 a b  c d e f

0x0- □□□□ □□□□ □□□□ □□□□ □□□□ □□□□ □□□□ □□□□

0x2- □□□□ □□□□ □□□□ □□□□ □□□□ □□□□ □□□□ □□□□

0x4- □□□□ □□□□ □□□□ □□□□ □□□□ □□□□ □□□□ □□□□

0x6- □□□□ □□□□ □□□□ □□□□ □□□□ □□□□ □□□□ □□□□



long이든 double이든 8Byte 사이즈의 메모리가 각각 할당되었습니다.



문제는... char나 short 입니다.

char이던 short이던 같은 타입의 변수를 연속적으로 할당받을 땐 상관 없지만 다른 종류의 데이터 타입을 섞어서 사용할 때에는 이상한 현상이 발생합니다.



char  chVal[4];

short shVal[2];



       0 1 2 3  4 5 6 7  8 9 a b  c d e f  0 1 2 3  4 5 6 7  8 9 a b  c d e f

0x0- □□□□ □□□□ □□□□ □□□□ □□□□ □□□□ □□□□ □□□□

0x2- □□□□ □□□□ □□□□ □□□□ □□□□ □□□□ □□□□ □□□□

0x4- □□□□ □□□□ □□□□ □□□□ □□□□ □□□□ □□□□ □□□□

0x6- □□□□ □□□□ □□□□ □□□□ □□□□ □□□□ □□□□ □□□□





그러나 여러종류의 변수를 복합적으로 선언할때는 이런 현상이 나타납니다.



*- 코드 --------------------------------------------------------------------------------*

// 헤더, 메인 생략

int    a;
char   b;
short  c;
char   d;
float  e;


printf( "[%d|%x]\n[%d|%x]\n[%d|%x]\n[%d|%x]\n",
          &a,&a, &b,&b, &c,&c, &d,&d, &e,&e );

*--------------------------------------------------------------------------------------*

*- 결과 --------------------------------------------------------------------------------*

[4358104|427fd8]

[4358108|427fdc]

[4358110|427fde]

[4358112|427fe0]

[4358116|427fe4]

*--------------------------------------------------------------------------------------*



이상한 점을 발견하셨나요?

이해를 돕기 위해 이것을 메모리 블럭으로 나타내면

             0 1 2 3  4 5 6 7  8 9 a b  c d e f  0 1 2 3  4 5 6 7  8 9 a b c d e f

0x427fd- □□□□ □□□□ □□□□ □□□□ □□□□ □□□□ □□□□ □□□□



일반적인 예상대로라면

             0 1 2 3  4 5 6 7  8 9 a b  c d e f  0 1 2 3  4 5 6 7  8 9 a b c d e f

0x427fd- □□□□ □□□□ □□□□ □□□□ □□□□ □□□□ □□□□ □□□□

가 되어야 하는데 말이지요. 0x427fdd 블럭과 0x427fe1-0x427fe3 블럭 구간이 남겨진 채 메모리가 할당되고 있습니다.



이것은 메모리 접근방식에 의한 차이라고 생각됩니다.

기본적으로 OS는 메모리에 int 단위(4byte)로 접근합니다. 만약 이 공간보다 더 많은 메모리 공간이 필요하다면 다음 블럭과 연결해서 메모리를 할당하구요, 만약 이보다 적다면 더 작은 공간(1/2, 1/4 순)으로 탐색해 들어갑니다. short나 char의 경우겠지요. 분할 된 공간에서 메모리가 할당이 되고, 다음 변수의 메모리 할당이 요구되면 동일한 접근 과정을 거쳐을 위해 공간을 검색하기 때문에 1/2공간이나 1/4공간은 다음 메모리 할당의 검색구간에서 제외가 되는 경우가 생기는 것으로 보입니다. 좀 더 복잡하죠?

요약하자면 기본 메모리 할당 포인터의 이동간격은 4Byte이고 이 블럭 안에서도 2진접근에 의해 메모리를 할당하고 부족하면 다음 공간에서 메모리를 할당한다라는 정도겠네요.

    



*- ※ 참고 -----------------------------------------------------------------------------*

제가 char, short를 마지막에 언급하고 메모리 블럭을 일부러 4byte 단위로 나눈것은 종전의 OS 메모리 관리체계상의 특징을 말씀드리려고 한거였었는데 코드를 실행해보니 예상밖의 결과가 나와 확인 해 보니.... XP로 오면서 메모리 관리효율이 높아져서... 라는군요. 무척 많이 바뀌었습니다 =_=;; 이럴때.. 이런 말을 쓰겠죠..



대략 낭패!!



그냥 이랬었다..라고만 알려드리자면 제가 배웠을 당시(win2000이 주류를 이루던)까지만해도 char, short 변수를 선언하면 변수가 할당되는 메모리는 int를 기준으로 4byte 단위로 끊어져 할당이 됐었답니다. 즉, 현재처럼 char 변수 여러개를 할당하면 메모리 주소가 연결되어 0x00, 0x01 로 할당되는 2진접근 과정은 아예 무시되고 0x00, 0x04로 할당이 됐었던 것입니다.(크윽 ㅠㅠ)



char  ch[4];

short sh[2];

현재처럼

0x0- □□□□ □□□□ □□□□ □□□□ □□□□ □□□□ □□□□ □□□□

이렇게 할당되는게 아니라



0x0- □□□□ □□□□ □□□□ □□□□ □□□□ □□□□ □□□□ □□□□

이렇게 할당이 됐었더란 말입니다.. =_=;;

그래서 할당된 주소만 보고는 int인지 char인지 short인지 구분을 할 수 없었죠.

게다가 메모리 효율도 떨어져서 char를 쓰나 int를 쓰나 그게그거였는데.. 이젠 정말 이런 낭비가 없어졌네요. 흠.. 저도 이거 쓰면서 처음 알았습니다.. 바보바보 ㅠㅠ



추가로 한가지 더 알려드리자면.. 메모리가 할당되는 순서도 지금처럼 앞에서 뒤로 방향(0x00, 0x01, 0x02,...)이 아니라.. 뒤에서 앞으로(0x7f, 0x7e, 0x7d,...)였습니다. 그래서 이글 거의 다 써 놓고 코드 작성 해 놓고 결과 붙이기 하려는 찰나에...... 싹 다 수정했습니다 에효 ㅠㅠ..

*--------------------------------------------------------------------------------------*





엄청나게 길어졌습니다. 조금이나마 도움 되는 부분이 있었으면 좋겠네요.

그럼 이번 스터디는 여기서 마치겠습니다.

ps.이제부터는 다른 닉네임(때릴꺼야)에 강의를 올리겠습니다.(꾸벅)양해 please(꾸벅)

  Hit : 7880     Date : 2011/05/01 11:22



    
/x7375696e6567 현재처럼
0x0- □□□□ □□□□ □□□□ □□□□ □□□□ □□□□ □□□□ □□□□
이렇게 할당되는 것이 아니라

0x0- □□□□ □□□□ □□□□ □□□□ □□□□ □□□□ □□□□ □□□□
이렇게 할당되더란 말입니다.
-----------------------------------------------------------------------------------------
뭐가 다른가요?
2011/05/01  
ani4ever 네이버 C 카페에서 활동하고 있는 윗글의 원저자입니다 ^^;; 마음대로 글을 퍼가도 상관없다고는 했지만... 이런 문제가 생길 줄은 미처 예상을 못했네요.
위에 질문하신 '뭐가 다른가요'... 사실 글자가 전부 검정으로 표기가 되서 차이를 알 수가 없는거에요.
-----------------------------------------------------------------------------------------
현재처럼
0x0- ■▤▥▨ ▧▧▦▦ □□□□ □□□□ □□□□ □□□□ □□□□ □□□□
이렇게 할당되는 것이 아니라

0x0- ■□□□ ▤□□□ ▥□□□ ▨□□□ ▧▧□□ ▦▦□□ □□□□ □□□□
이렇게 할당되더란 말입니다.
-----------------------------------------------------------------------------------------
이렇게 설명드리면 무슨 차인지 잘 보이시겠죠? ^^;;

다음부터는 글 퍼가실 때 출처를 명기해 달라고 요청해야겠네요.
2011/05/14