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)과 같은 추가적인 정보를 포함하고 있습니다. 컴파일러의 구조를 한번 공부해보면 도움이 될 것 같네요...


보통 1차원 배열의 배열 이름은 포인터를 의미한다...
예를 들어  int arr[5];에서 arr은 int 형 포인터 변수이다...

그러면 2차원 배열에서의 배열 이름은 무엇을 의미할까??

2차원 배열의 배열 이름도 역시나 포인터를 의미하는데, 이 포인터의 포인터 타입은 무엇일까?
포인터 타입에는 이동에 대한 정보가 들어 있어야 한다.
예를 들어 int arr[2][4];에서 arr을 포인트 연산 arr++을 해보면 포인터 주소가 16씩 바뀌는 걸 알 수 있다.
그러므로 위의 2차원 배열 이름인 arr의 포인트 타입은 포인터 연산시 배열요소를 4칸씩 건너뛰는 int형 포인터 변수이다.

int (*arr)[4]  //  int arr[2][4] 이 둘은 포인터 타입이 같다...

참고로 int *arr[4]은 배열의 선언이다... 이것은 int형 포인터 변수 4개를 저장할 수 있는 배열을 선언하는 것이다.


+ Recent posts