1. 1바이트는 8개의 비트로 이루어집니다. 255 8개 비트 전체가 1인 경우이고, 1은 최하위 1비트만 1인 경우입니다. 문자 ch의 켜진 비트 개수는 몇 개입니까? 켜진 비트는 1로 설정된 비트의 다른 표현입니다.

대문자 ‘A’의 값은 65이고 6번째와 1번째 비트가 켜져 있습니다.


 Character.h
#ifndef CHARACTER_H
#define CHARACTER_H

class Character
{
public:  
  // 생성자
  Character();
  Character(char c);

  // 소멸자
  ~Character();

  void setChar(char c);
  char getChar() const;
  int bitCount() const;

protected:
  char data;
};

#endif

 Character.cpp
#include "Character.h"

void Character::setChar(char c)
{
  data = c;
}

char Character::getChar() const
{
  return data;
}

int Character::bitCount() const
{
  int cnt = 0;
  int tmp = 1;

  for(int i = 0 ; i < 8 ; ++i)
  {
    if(data & tmp)
    {
      ++cnt;
    }
    tmp <<= 1;
  }
  return cnt;
}

 main.cpp
#include "Character.h"
#include <iostream>
using namespace std;

int main()
{
  char c;

  cout << "문자 입력: ";
  cin >> c;

  Character ch(c);
  cout << "켜진 비트의 개수: " << ch.bitCount() << endl;
  cout << "값: " << (int)ch.getChar() << endl;
  
  return 0;
}


2. 정수 N이 소수입니까?


 Integer.h
#ifndef INTEGER_H
#define INTEGER_H

class Integer
{
public:
  // 생성자
  Integer();
  Integer(int num);

  // 소멸자
  ~Integer();

  // 멤버 함수
  void setNum(int num);
  int getNum() const;
  int sumNum() const;
  bool isPrime() const;  // 소수 여부 확인 해주는 함수

protected:
  int num;
};

#endif

 Integer.cpp
#include "Integer.h"

void Integer::setNum(int num)
{
  this -> num = num;
}

int Integer::getNum() const
{
  return num;
}

int Integer::sumNum() const
{
  int result;
  int tmp = num;

  result = tmp / 100;    // 100자리
  tmp %= 100;
  result = result + (tmp / 10);  // 100자리 + 10자리
  tmp %= 10;
  result += tmp;    // 100자리 + 10자리 + 1자리

  return result; 
}

// 소수 여부 확인 해주는 함수
bool Integer::isPrime() const
{
  int i;

  if(2 == num)
  {
    return true;
  }
  else if(num % 2 == 0)
  {
    return false;
  }

  for(i = 3 ; i <= num ; i += 2)
  {
    if(num % i == 0)
    {
      break;
    }
  }
  if(i == num)
  {
    return true;
  }
  else
  {
    return false;
  }
}

Integer::Integer()
{
  setNum(0);
}

Integer::Integer(int num)
{
  setNum(num);
}

Integer::~Integer()
{

}

 main.cpp
#include "Integer.h"
#include <iostream>
using namespace std;

int main()
{
  int num;

  cout << "정수 입력: ";
  cin >> num;

  Integer i(num);

  if(i.isPrime())
    cout << "결과 : 소수" << endl;
  else
    cout << "결과 : 합성수" << endl;
  
  return 0;
}



3. 로또 번호 생성 프로그램


 Lotto.h
#ifndef LOTTO_H
#define LOTTO_H

class Lotto
{
public:
  // 생성자
  Lotto();

  // 소멸자
  ~Lotto();

  // 멤버 함수
  void createRandom();
  void printNum() const;

protected:
  int num[6];

  bool sameNum(const int *arr, int size, int num) const;

};

#endif

 Lotto.cpp
#include "Lotto.h"
#include <iostream>
#include <stdlib.h>
using namespace std;

bool Lotto::sameNum(const int *arr, int size, int num) const
{
  for(int i = 0 ; i < size ; ++i)
  {
    if(arr[i] == num)
    {
      return 1;
    }
  }
  return 0;
}

void Lotto::createRandom()
{
  int tmp;
  
  for(int i = 0 ; i < 6 ; ++i)
  {
    tmp = (rand() % 45) + 1;
    while(sameNum(num, i + 1, tmp))
    {
      tmp = (rand() % 45) + 1;
    }
    num[i] = tmp;
  }
}

void Lotto::printNum() const
{
  for(int i = 0 ; i < 6 ; ++i)
  {
    cout.width(2);
    cout << num[i] << " ";
  }
  cout << endl;
}

Lotto::Lotto()
{
  createRandom();
}

Lotto::~Lotto()
{

}


 main.cpp
#include "Lotto.h"
#include <iostream>
#include <time.h>
#include <stdlib.h>
using namespace std;

int main()
{
  int cnt;

  srand((unsigned int)time(NULL));

  cout << "Enter the Game count: ";
  cin >> cnt;

  cout << "========================" << endl;
  for(int i = 0 ; i < cnt ; ++i)
  {
    Lotto l;
    l.printNum();  
  }
  cout << "========================" << endl;

  return 0;
}



SRCS    = $(wildcard *.cpp)
CC        = g++
TARGET    = main

all : $(TARGET)

$(TARGET) : $(SRCS:.cpp=.o)
    $(CC) -o $@ $^

clean :
    rm -rf $(SRCS:.cpp=.o)

// 타겟을 main으로 하는 c++ Makefile... 디렉토리 안의 모든 cpp 파일을 컴파일, 링크해서 main 실행 파일을 만들어 준다.



 Rect.h
#ifndef RECT_H
#define RECT_H

#include "Point.h"

class Rect
{
public:
  // 생성자
  Rect();

  // 각 점의 값 지정/얻기
  void SetTopLeft(const Point& topLeft);
  void SetBottomRight(const Point& bottomRight);
  void SetRect(int left, int top, int right, int bottom);
  Point GetTopLeft() const;
  Point GetBottomRight() const;
  void GetRect(int& left, int& top, int& right, int& bottom);

  // 넓이, 높이 계산
  int GetWidth() const;
  int GetHeight() const;

  // 내 용 출력
  void Print() const;

protected:
  Point _topLeft;
  Point _bottomRight;
};

#endif

 Rect.cpp
#include "Rect.h"
#include <iostream>
using namespace std;

Rect::Rect()
{

}

void Rect::SetTopLeft(const Point& topLeft)
{
  _topLeft = topLeft;
}

void Rect::SetBottomRight(const Point& bottomRight)
{
  _bottomRight = bottomRight;
}

void Rect::SetRect(int left, int top, int right, int bottom)
{
  _topLeft.SetX(left); 
  _topLeft.SetY(top); 
  _bottomRight.SetX(right); 
  _bottomRight.SetY(bottom); 
}

Point Rect::GetTopLeft() const
{
  return _topLeft;
}

Point Rect::GetBottomRight() const
{
  return _bottomRight;
}

void Rect::GetRect(int& left, int& top, int& right, int& bottom)
{
  left = _topLeft.GetX(); 
  top = _topLeft.GetY(); 
  right = _bottomRight.GetX(); 
  bottom = _bottomRight.GetY(); 
}

int Rect::GetWidth() const
{
  if(_bottomRight.GetX() > _topLeft.GetX())
  {
    return (_bottomRight.GetX() - _topLeft.GetX() + 1);
  }
  else
  {
    return (_topLeft.GetX() - _bottomRight.GetX() + 1);
  }
}

int Rect::GetHeight() const
{
  if(_bottomRight.GetY() > _topLeft.GetY())
  {
    return (_bottomRight.GetY() - _topLeft.GetY() + 1);
  }
  else
  {
    return (_topLeft.GetY() - _bottomRight.GetY() + 1);
  }
}

void Rect::Print() const
{
  cout << "{L=" << _topLeft.GetX() << ", T=" << _topLeft.GetY();
  cout << ", R=" << _bottomRight.GetX() << ", B=" <<
    _bottomRight.GetY() << "}\n";
}

 main.cpp
#include "Rect.h"
#include <iostream>
using namespace std;

int main()
{
  // Rect 객 체 생성
  Rect rc1;

  // 값을 바 꿔본다.
  rc1.SetRect(101000);

  // 내용 출력
  rc1.Print();
  
  // 넓이, 높이 출력
  cout << "rc1.GetWidth() = " << rc1.GetWidth() << endl;
  cout << "rc1.GetHeight() = " << rc1.GetHeight() << endl;

  // 값을 바꿔본다.
  rc1.SetRect(10102020);

  // 내용 출력
  rc1.Print();

  // 넓이, 높이 출력
  cout << "rc1.GetWidth() = " << rc1.GetWidth() << endl;
  cout << "rc1.GetHeight() = " << rc1.GetHeight() << endl;

  return 0;
}


출처 : http://blog.naver.com/psychoria?Redirect=Log&logNo=40108323175

C++은 메모리 관리를 하기가 상당히 까다로운 언어입니다.

특히 동적 할당과 관련해서는 조금만 실수하면

메모리 누수나, 메모리 접근 위반으로 프로그램이 죽기가 일쑤입니다.

메모리 누수란 프로그램이 종료시까지는 할당한 모든 메모리가해제가 되어야 하는데

할당된 메모리가 해제가 되지 않을 때 발생하는 문제이고,

메모리 접근 위반은 초기화되지 않은 포인터 혹은 동적 할당된 범위 밖을 호출할 때 발생합니다.

초기화되지 않은 포인터는 어떻게 작동할 지 예측을 할 수 없습니다.

(10중 8, 9는 프로그램이 죽는다고 보면 됩니다.)

new 사용 시에 주의할 점은 다음과 같습니다.

new로 할당 시에는 delete로 해제하고, new[]로 할당 시에는 delete[]

해제해줘야 한다는 것입니다.

C++을 공부한 입장에서는 너무도 당연한 이야기겠지만,

사실 신경쓰지 않고 사용했다가 나중에 메모리 누수가 났을 때,

디버깅시에 상당히 오랜 시간을 투자해야할 수도 있기 때문에 꼭 다시 한 번 상기하시기 바랍니다.

Effective C++에서는 다음과 같은 그림을 들어서 new를 delete[]로 해제했을 때나

new[]를 delete로 해제했을 때의 위험성을 설명합니다.


 
한 개를 해제하려고 하는데 갯수를 참조하게 되고,
 
배열을 해제하려고 하는데 크기 정보를 못 가져 오는 상황을 의미하는 거 같습니다.
 
실제 코드를 통해서 알아보도록 하겠습니다.
 
console project에서 확인하기 위해서
 
#define _CRTDBG_MAP_ALLOC

#include <crtdbg.h>
 
를 추가합니다.


 
F5를 눌러서 Debug를 실시해보면
 
delete가 호출되는 시점 이전의 _CrtDumpMemoryLeaks() 함수를 호출할 때
 
출력창에 다음과 같이 표시됩니다.
 

 
Detected memory leaks! 라는 문장이 보이네요.
 
메모리 누수가 생긴것이지요.
 
기본에 충실해야 더 나아갈 수 있는 것 같습니다.

1. 문자가 숫자인지 알려주는 isDigit 함수를 구현하세요. 문자 ch를 입력 받고, ch가 숫자인지 출력하세요. 맞다면 YES, 아니면 NO를 출력합니다.


 character.h
#ifndef CHARACTER_H
#define CHARACTER_H

class Character
{
public:
  // 생성자
  Character();
  Character(char c);

  // 멤버 함수
  void setChar(char c);
  int getChar() const;
  bool isDigit() const;
protected:
  // 멤버 변수
  char ch;
};

#endif

 character.cpp
#include "Character.h"

void Character::setChar(char c)
{
  ch = c;
}

int Character::getChar() const
{
  return ch;
}

bool Character::isDigit() const
{
  if(('0' <= ch) && ('9' >= ch))
  {
    return 1;
  }
  else
  {
    return 0;
  }
}

Character::Character()
{
  ch = 0;
}

Character::Character(char c)
{
  setChar(c);
}

 main.cpp
#include "Character.h"
#include <iostream>
using namespace std;

int main()
{
  char ch;
  
  cout << "문자: ";
  cin >> ch;

  Character c(ch);
  if(c.isDigit())
  {
    cout << "결과: YES" << endl;
  }
  else
  {
    cout << "결과: NO" << endl;
  }
}


2. 정수 N을 입력하면 2 N승을 출력하는 프로그램을 작성하세요. , 비트 연산을 이용합니다.

그리고 -1을 입력할 경우 프로그램은 종료됩니다.


 Multiplier.h
#ifndef MULTIPLIER_H
#define MULTIPLIER_H

class Multiplier
{
public:
  // 생성자
  Multiplier();
  Multiplier(int num);

  // 멤버 함수
  void setNum(int num);
  int getNum() const;
  void twoMulti(int num);
  int getResult() const;
protected:
  int num;  // 승수
  int result;  // 결과
};

#endif

 Multiplier.cpp
#include "Multiplier.h"

void Multiplier::setNum(int num)
{
  this -> num = num;
}

int Multiplier::getNum() const
{
  return num;
}

void Multiplier::twoMulti(int num)
{
  result = 1 << num;  
}

int Multiplier::getResult() const
{
  return result;
}

Multiplier::Multiplier()
{
  num = 0;
  result = 0;
}

Multiplier::Multiplier(int num)
{
  setNum(num);
  twoMulti(num);
}

 main.cpp
#include "Multiplier.h"
#include <iostream>
using namespace std;

int main()
{
  int num;

  while(1)
  {
    cout << "입력: ";
    cin >> num;

    if(num == -1)
    {
      return -1;
    }

    Multiplier m(num);

    cout << "출력: 2 ^ " << m.getNum() << = " << m.getResult() <<endl;
  }

  return 0;
}

source
#include <iostream>
using namespace std;

class DynamicArray
{
private:
  int **array;
  int x;    // x축
  int y;    // y축
public:
  // 생성자
  DynamicArray();
  DynamicArray(int x, int y);
  DynamicArray(const DynamicArray& da);
  
  // 소멸자
  ~DynamicArray();

  // 멤버 함수
  int getX() const;
  int getY() const;
  void allocate(int x, int y);
  void free();
  void initArray();
  void printArray();
};

// 멤버 함수
int DynamicArray::getX() const
{
  return x;
}

// 멤버 함수
int DynamicArray::getY() const
{
  return y;
}

// 멤버 함수
void DynamicArray::allocate(int x, int y)
{
  this->= x;
  this->= y;

  // 동적 메모리 할당
  array = new int* [x];
  for(int i = 0 ; i < x ; ++i)
  {
    array[i] = new int[y];
  }
}

// 멤버 함수
void DynamicArray::free()
{
  // 동적 메모리 해제
  for(int i = 0 ; i < x ; ++i)
  {
    delete[] array[i];
  }
  delete[] array;
}

// 멤버 함수
void DynamicArray::initArray()
{
  int cnt = 0;

  for(int i = 0 ; i < x ; ++i)
  {
    for(int j = 0 ; j < y ; ++j)
    {
      array[i][j] = cnt;
      ++cnt;
    }
  }
}

// 멤버 함수
void DynamicArray::printArray()
{
  for(int i = 0 ; i < x ; ++i)
  {
    for(int j = 0 ; j < y ; ++j)
    {
      cout << array[i][j] << endl;
    }
  }
}

// 디폴트 생성자
DynamicArray::DynamicArray()
{
  array = NULL;  
}

// 인자가 있는 생성자
DynamicArray::DynamicArray(int x, int y)
{
  allocate(x, y);
}

// 복사 생성자
DynamicArray::DynamicArray(const DynamicArray& da)
{
  x = da.getX();
  y = da.getY();
  allocate(x, y);

  for(int i = 0 ; i < x ; ++i)
  {
    for(int j = 0 ; j < y ; ++j)
    {
      array[i][j] = da.array[i][j];
    }
  }
}

// 소멸자
DynamicArray::~DynamicArray()
{
  free();
}

int main()
{
  DynamicArray da(57);

  da.initArray();
  da.printArray();

  return 0;
}

1. 1바이트는 8개의 비트로 이루어집니다. 255 8개 비트 전체가 1인 경우이고, 1은 최하위 1비트만 1인 경우입니다. 문자 ch의 켜진 비트 개수는 몇 개입니까? 켜진 비트는 1로 설정된 비트의 다른 표현입니다.

대문자 ‘A’의 값은 65이고 6번째와 1번째 비트가 켜져 있습니다.

#include <iostream>
using namespace std;

int main()
{
  char c;
  int cnt = 0;
  int tmp = 1;

  cout << "문자 입력: ";
  cin >> c;

  for(int i = 0 ; i < 8 ; ++i)
  {
    if(c & tmp)
    {
      ++cnt;
    }
    tmp <<= 1;
  }
  cout << "켜진 비트의 개수: " << cnt << endl;
  cout << "값: " << (int)c << endl;

  return 0;
}


2. 정수 N이 소수입니까?

#include <iostream>
using namespace std;

int main()
{
  int num;
  int cnt = 0;

  cout << "정수 입력: ";
  cin >> num;

  for(int i = 1 ; i <= num ; i++)
  {
    if(num % i == 0)
    {
      ++cnt;
    }
  }

  if(cnt == 2)
  {
    cout << "결과 : 소수" << endl;
  }
  else
  {
    cout << "결과 : 합성수[" << cnt << "]" << endl;
  }

  return 0;
}


3. 로또 번호 생성 프로그램

    • 사용자에게 로또 게임 수를 입력받아 게임의 횟수별로 임의로 6개의 수(1~45)를 자동 생성하는 프로그램을 작성하시오. , 생성된 난수는 모두 다른 값이어야 한다.
    •  로또 번호는 정수형 배열을 선언하여 설정한다.
    • 프로그램의 주요한 부분에 주석을 명시하시오.
#include <iostream>
using namespace std;

bool sameNum(const int *arr, int size, int num);

int main()
{
  int num[6];  // 생성된 난수 보관 하는 변수
  int cnt;
  int tmp;  // 난수 값을 임시로 받는 변수

  srand((unsigned int)time(NULL));

  cout << "Enter the game count: ";
  cin >> cnt;

  for(int i = 0 ; i < cnt ; ++i)
  {
    for(int j = 0 ; j < 6 ; ++j)
    {
      tmp = (rand() % 45) + 1;
      while(sameNum(num, j + 1, tmp))  // 같은 값이 있으면
      {
        tmp = (rand() % 45) + 1;  // 새로 값을 받음
      }
      num[j] = tmp;
    }

    // 생성된 난수 출력
    cout << "game" << i << " : ";
    for(int j = 0 ; j < 6 ; ++j)
    {
      cout.width(2);
      cout << num[j] << " ";
    }
    cout << endl;
  }

  return 0;
}

// 같은 값이 있는지 확인해 주는 함수
bool sameNum(const int *arr, int size, int num)
{
  for(int i = 0 ; i < size ; ++i)
  {
    if(arr[i] == num)
    {
      return 1;
    }
  }
  return 0;
}




1. 키보드로부터 최대 세 자리의 정수를 입력 받습니다. 자릿수들의 합계는 얼마입니까?

#include <iostream>
using namespace std;

int main()
{
  int num;
  int result;

  while(1)
  {
    cout << "입력(3자리): ";
    cin >> num;

    if(-1 == num)
    {
      break;
    }
    else if((0 > num) || (999 < num))
    {
      cout << "3자리이하의 수를 입력하시오" << endl;
    }
    else
    {
      result = num / 100;    // 100 자리 
      num %= 100;
      result = result + (num / 10);  // 100 자리 + 10자리
      num %= 10;
      result += num;    // 100 자리 + 10 자리 + 1자리
      cout << "자릿수 합계: " << result << endl;
    }
  }
  cout << "프로그램 종료" << endl;
  
  return 0;
}


2. 10진수 0부터 16까지의 정수를 8진수로 출력합니다. 출력에는 10진수와 8진수의 대응관계를 반드시 포함시킵니다. 10진수 8 8진수로 10입니다.

#include <iostream>
using namespace std;

int main()
{
  cout << "10진수\t8진수" << endl;

  for(int i = 0 ; 17 > i ; i++)
  {
    cout << dec << i << "\t";  // 10진수 출력
    cout << oct << i << endl;  // 8진수 출력
  }

  return 0;
}


3. 1바이트 범위의 정수를 입력 받은 다음, 각각의 비트가 켜져 있으면 1, 꺼져 있으면 0을 출력하세요.

#include <iostream>
using namespace std;

int main()
{
  int num;
  char tmp;
  int cnt = 0;

  while(1)
  {
    cout << "정수 입력(0~255): ";
    cin >> num;

    if(-1 == num)
    {
      cout << "프로그램 종료" << endl;
      break;
    }

    for(tmp = 1 ; 0 != tmp ; tmp <<= 1)
    {
      if(num & tmp)  // 각 자리 수가 1이면 
      {
        cout << cnt << " : 1" << endl;
      }
      else
      {
        cout << cnt << " : 0" << endl;
      }
      cnt++;
    }
    cnt = 0;
  }

  return 0;
}


4. 난수를 발생하는 rand 함수를 활용하여 주사위를 10번 던졌을 때 주사위 값을 출력하시오.

#include <iostream>
using namespace std;

int main()
{
  int num;

  srand((unsigned int)time(NULL));

  cout << "(주사위 값 : 1 ~ 6)" << endl;
  for(int i = 0 ; 10 > i ; i++)
  {
    num = (rand() % 6) + 1;
    cout << "dice value: " << num << endl;
  }
  return 0;
}


struct map_form
{
        char sBsnsDate[8]; 
        char sCount[4];  
        struct ITEM item[1000]; /* ITEM의 크기는 88 */
};

위의 구조체 크기는 88012 일거라고 생각했다. 하지만 sizeof(struct map_form) 결과, 88016 이 나왔다.

메모리 할당 문제이다. 아래에 자세한 설명이 있다.

구조체가 메모리에 할당될 때는 일정한 규칙을 가진다. 일반적으로 우리가 생각하기에 어떤 구조체가 메모리에 할당될 때 
구조체의 첫 번째 멤버부터 순서대로 메모리에 차곡차곡  쌓여간다고 생각한다.

예를 들어 다음과 같은 구조체가 있다고 생각해보자.

struct StructA{
    char a;
    int b;
    char c;
    int d;
};

일반적으로 생각하기에 위의 구조체가 메모리에 할당되는 그림은 다음과 같다
[char a 1byte][int b 4byte][char c 1byte][int d 4byte]

그런데 문제는 이렇게 해서 전체 10바이트가 할당이 될 거라고 생각하면 오산 이라는 것이다. 
실제로 메모리에 할당되는 그림은 다음과 같다.
[char a 1byte][padding 3byte][int b 4byte][char c 1byte][padding 3byte][int d 4byte]

우선 눈에 들어오는 부분은 중간 중간에 padding데이터가 끼어있다는 것이다. 
좀더 자세히 살펴보면 메모리를 4바이트씩 묶을 수 있다는 사실을 발견할 수 있다. 그럼 반대로 4바이트씩 떼어서 보자.
[char a 1byte, padding 3byte] [int b 4byte] [char c 1byte, padding 3byte] [int d 4byte]

자 이렇게 보면 4바이트씩 데이터가 구분되어 있다는 것 이다. 이런 식으로 메모리를 사용하는 이유는 데이터를 읽고 쓸 때 보다 효율적으로 사용하기 위해서다. 
메모리에 할당할 때 4바이트씩 그 4바이트 안에 들어가지 않으면 나머지는 비워두고 다음 4바이트가 되는 주소에 넣어버리는 것이다. 때문에 위에서 보이는 것처럼 char a 다음에 int b가 바로 오지 않고 padding이 3바이트가 있는 것이다.

이렇게 구조체가 메모리에 할당될 때 일정한 규칙이 있는데,

1. 구조체의 멤버들 중에 가장 큰 데이터형을 찾는다. 
    -> int형이 가장 큰 데이터형 이므로 4byte
2. 그 데이터형의 크기와 맞게 메모리를 쪼갠다.
    -> 4byte씩 메모리를 나눈다. [4byte][4byte][4byte][4byte][4byte]
3. 멤버들을 차곡차곡 쌓아 넣는다.
    a. char a를 넣는다.
    [char a]
    b. int b를 넣어야 되는데 char a가 들어가 있는 곳에는 3바이트밖에 남아있지 않으므로 다음 메모리에 넣는다.
    [char a 1byte, padding 3byte][int b 4byte]
    c. char c를 넣는다.
    [char a 1byte, padding 3byte][int b 4byte][char c 1byte]
    d. int d를 넣어야 되는데 char c가 들어가 있는 곳에는 3바이트밖에 남아있지 않으므로 다음 메모리에 넣는다.
    [char a 1byte, padding 3byte][int b 4byte][char c 1byte, padding 3byte][int d 4byte]

이런 식으로 할당되는 것이다.

그런데 padding으로 채워진 부분 때문에 문제가 발생할 경우가 있으므로 그 padding을 없애는 방법을 알아보자.
결론부터 말하자면
#pragma pack(n) //n = 1, 2, 4, 8, 16 ...

구조체 선언부 위에 이 문장을 삽입하면 된다. 이것의 의미는 구조체를 정렬할 때 n바이트 단위로 정렬을 하라는 이야기다. 지정해준 n과 구조체의 멤버 중 가장 큰 멤버의 크기와 비교해서 더 작은 것에 맞추어서 정렬을 한다.

기본적으로 정렬을 할 때 멤버의 가장 큰 데이터형을 찾아서 메모리에 정렬을 하도록 되어있지만 위의 문장은 멤버 데이터형의 크기보다 작은 n바이트 정렬을 가능하게 한다. 그렇게 해서 padding을 없애는 것이다.

위의 예제를 아래와 같이 수정해보자

#pragma pack(2) //2바이트로 메모리를 정렬
struct StructA{
    char a;
    int b;
    char c;
    int d;
}

이렇게 한다면 메모리에 할당되는 그림은 아래와 같을 것이다.
[char a 1byte, padding 1byte][int b 2byte][int b 2byte][char c 1byte, padding 1byte][int d 2byte][int d 2byte]
padding이 2바이트로 줄었다. 

그리고 만약 #pragma pack(1) 로 설정한다면 padding은 완전히 사라지고 구조체의 크기는 10바이트로 정확히 맞추어질 것이다. 
#pragma pack(1)을 사용한 후 메모리 모습
[char a 1byte][int b 1byte][int b 1byte][int b 1byte][int b 1byte]
[char c 1byte][int d 1byte][int d 1byte][int d 1byte][int d 1byte]

프로그래밍을 공부하기 위해서는 언어도 필수 이지만 시스템에 대한 이해도 필수 입니다.(...라고 본인은 생각합니다;;) 시스템에서도 핵심은 메모리 인데요...메모리 구조를 제대로 알아야 최적화를 비롯한 자료구조 등의 프로그래밍을 쉽게 공부 할 수 있습니다.

 

 위의 그림은 C에서의 메모리 레이아웃을 나타낸 그림입니다. 아마 프로그래밍을 공부하신 분이라면 한번 쯔~음은 보셨을 것입니다.

 우선 ROM부터 설명하겠습니다. ROM은 일반적인 데스크탑이라면 하드디스크(HDD)일 것이고, 임베디드 시스템이라면 보통 Flash ROM을 말합니다... 컴파일로 생성된 실행 파일이나...시스템이 동작하기 위한 이미지 파일이 ROM에 저장되어 있을 것입니다.

 ROM의 Text 섹션은 보통 프로그램 코드 (함수나 일반 처리문 같은....)와 읽기 전용 데이터(RO_data, 상수 데이터)로 이루어져있습니다. 

 Data 섹션은 읽기 쓰기가 가능한 (RW_data) 데이터가 들어가는데 그 예로 전역 변수나 정적 변수를 들 수 있습니다.

 ROM - Text : Code + RO_data

           Data : RW_data


 RAM은 최근 대부분의 구성이 보호 모드로 가상 메모리를 사용하게 되어있습니다. 물론 실제 모드로 위의 그림과 같이 세그먼트를 분리하여 사용하기도 하지만 그래도 가상 메모리 구조와 별차이 없게 사용하고 있습니다.

 시스템을 구동 할 경우 어셈블리로 스타트업 코드를 구현하다가 C언어의 main()으로 제어를 넘기는데 그 이유가 위와 같이 레이아웃이 설정되어 있어야 하기 때문입니다. 어셈블리로 구현된 스타트업 코드에서 가상메모리 페이지를 관리하는 MMU(Memory Management Unit)를 구동시키거나 세그먼트를 분리해야 C 코드로 제어를 넘길 수 있습니다.

 가상 메모리는 말그대로 보호 모드(말 그대로 각 프로세스. 즉, 메모리에 올라와 실행되는 프로그램마다 각자 고유의 메모리 영역을 침범되지 않게 보호되어야 하는...)로 프로세스마다 32비트 시스템을 경우 4GB(2의 32승)의 가상메모리 영역을 가지게 되고, 각 프로서스마다 공유되는 커널 영역과 프로세스마다 고유한 유저 영역 메모리의 구조를 가지게 됩니다. 실제로 프로그램의 메모리 영역을 안다는 것이 위의 구조 중 유저영역을 이해한다는 것과 같다고 보면 될것입니다. 보통 윈도우즈의 경우 하위(0x0000 0000 ~ 0x7FFF FFFF) 2GB가 유저 영역, 상위 2GB(0x8000 0000 ~ 0xFFFF FFFF)의 커널 영역을 가지고 , 리눅스의 경우 상위 1GB 커널, 하위 3GB 유저 영역을 가집니다. (지역 변수의 경우 위의 그림의 경우 스택에 저장되기 때문에 & 연산자를 이용해 주소 값을 보면, 윈도우즈일 경우 주소가 0x7FFF FFFFF 보다 작으면서 가까운 주소가 출력 될 것입니다.)

 RAM의 Text영역은 ROM의 Text 영역을 그대로 가지고 옵니다. 하지만 임베디드 시스템의 경우 고정된 값이기 때문에 RAM으로 옮길 필요는 없습니다. 그래서 ROM에서 그대로 활용할 경우도 있으며 RAM공간에 여유가 있고 빠른 속도(RAM이 CPU에 가까우므로 빠르죠...)를 필요로 할 경우 RAM에 복사를 해서 사용 합니다.

 Data 영역은 그대로 ROM의 Data 영역을 복사해서 사용합니다.

 bss 영역은 ZI_data(Zero Initialoze). 즉, 초기화 되지 않은 전역 변수나 정적 변수로 구성된 세그먼트 입니다. 아마도 실행 파일과 같은 이미지 파일에서 메타 데이터 정보만 가지고 있다가 실제 RAM에 구성될 때 공간을 확보하는 것 같습니다. 실제 데이터가 없는데 ROM에 공간을 사용하는 것은 낭비니까 당연하겠네요...

 Heap은 동적 메모리가 할당되는 영역입니다. (완전 이진 트리를 나타내는 자료 구조인 Heap과 헛갈리시면 않됨미...) C의 mallloc()이나 C++의 new 할당자로 공간을 확보합니다. 사용 완료 후 해제를 해야하겠지요...이 영역은 단편화(Fragmentation) 문제와 관련된 영역이기도 합니다.

 Stack은 지역 변수를 기억하는 공간으로 LIFO 구조를 가집니다. 이 영역이 다른 영역에 비해서 복잡하면서도 구조를 제대로 이해하면 조금 재미있는 일(?)을 할 수 있습니다. 이 구조를 이해하기 위해서 스택 프레임(Stack Frame)을 한번 쯤 공부해보면 좋습니다.

RAM - Text : Code, RO_data

        Data : RW_data

        bss   : ZI_data

        Heap : 동적 할당 data

        Stack : LIFO구조로 저장되는 지역 변수 data

 

 물론 위의 가상 메모리 구조는 기본틀이고, 심볼 테이블(Symbol Table)과 같은 추가적인 정보를 포함하고 있습니다. 컴파일러의 구조를 한번 공부해보면 도움이 될 것 같네요...


+ Recent posts