메모리 주소를 사용하는 데이터 타입은 unsigned long 을 사용해야 한다

 

32bit 에서는 메모리 주소가 4바이트

64bit 에서는 메모리 주소가 8바이트

를 차지하므로 CPU 아키텍처에 따라 데이터 타입의 크기도 달라져야 한다

 

unsigned long 은 32bit 에서는 4바이트 64bit 에서는 8바이트가 되므로

 

메모리 주소를 담을때 사용가능하다.

 

ex)

char c;

unsigned long addr = (unsigned long)&c;


2바이트 이상의 문자를 문자를 저장하기 위해 wchar_t 변수를 사용한다.
물론 char * 로 저장할수도 있다.

wchar_t 는 typedef unsigned long (리눅스) 혹은 short(윈도우) 형이다.

리눅스에서는 4바이트이고 윈도우에서는 2바이트이다.

왜 wchar_t 를 사용하는가?

만약 char * 형으로 문자열(UTF-8 방식)을 저장한다고 하면,
"가" 란 글자는 EA B0 80 (16진수) 형태로 저장된다.
즉,
char *pHangul = "가"; 라고 선언하면,
pHangul[0] -> 0xEA;
pHangul[1] -> 0xB0;
pHangul[2] -> 0x80;

가 저장된다.

이 상태에서 만약 알파벳과 한글을 혼용하면, 영어는 1바이트 한글은 3바이트 이므로, 한글과 영어를 구분하여 사용하기가 상당히 난해한 상황이 온다.
일일이 최상위 비트를 검사해서 '1' 인지 확인한후 '1' 이면 뒤에 2바이트를 더 읽어들이는 작업을 해야 한다.
잘못 계산하면 3바이트중 엉뚱한 곳부터 시작하게 되면 엉뚱한 결과가 나오게 된다.

wchar_t 를 사용하면 한글과 영어에 상관없이 wchar_t 하나당 하나의 문자가 저장된다.
예를 들어 wchar_t *pw = L"ab가나"; 라고 하면 
pw[0] -> 'a';
pw[1] -> 'b';
pw[2] -> '가';
pw[3] -> '나';

이런식으로 저장이 된다. 그러므로 굳이 최상위 비트를 비교하지 않아도 된다.

char * 이런식으로 저장되는 형식을 Multi Byte String 이라고 하며
wchar_t 에 저장되는 형식을 Wide Character String 이라고 한다.

wchar_t 사용하기

wchar_t 는 일반적으로 유니코드 형식으로 저장된다.
문자열을 대입하기 위해서는 접두어 "L" 을 붙여준다.

wchar_t *str = L"한글";

이런식으로 사용한다.

wchar_t 를 다루는 표준함수가 있다.

기본적으로 C 의 char * 문자 관리 함수와 같다. 대신 str 대신 wcs 를 적어주면 된다.
strlen -> wcslen
strcpy -> wcscpy
...

wchar_t 를 출력하기위해서는 wprintf 또는 fputws 함수를 사용하면 된다.

리눅스에서는 wprintf 를 사용하면 printf 와 같이 사용할 수 없다.

wchar_t 와 char 변환

만약 소켓통신을 한다고 할때,
테이터를 wchar_t 로 받는 것보다 char * 로 받아서 형변환을 해주는게 손쉬울 것이다.
데이터를 전송할때도 wchar_t 를 char * 로 바꿔서 전송해주는것이 호환성을 위해 좋을 것이다.
(프로그램간에 프로토콜이 정해져 있으면, wchar_t 든 char 든 크게 상관은 없다.)

wchar_t 를 char 로 변환하려면 함수를 사용하면 된다.
char 를 wchar_t 로 변환하려면 mbstowcs 함수를 사용하면 된다.


리눅스 GCC vs VC++

리눅스에서 wchar_t 를 wprintf 로 출력하기 위해서는 몇가지 세팅이 필요하다.
리눅스에서는 여러가지 한글 인코딩을 사용할 수 있는데, 여기서는 utf8 을 기준으로 설명하도록 하겠다.

리눅스 콘솔 세팅이 UTF-8 로 되어 있는지 확인한다.
그리고 LANG 환경변수가 ko_KR.utf8 로 되어 있는지 확인한다.
ko_KR.eucKR 로 되어 있다면 ko_KR.utf8 로 바꿔준다.
바꾸게 되면 한글로 된 것들이 깨져 보일수 있다.
(어디까지나 UTF-8 테스트용이므로 그냥 넘어가도록 한다.)

그리고 wprintf(L"한글"); 을 이용해서 제대로 출력되는지 확인해 본다.
만약 제대로 나오지 않는다면,
setlocale(LC_ALL,"ko_KR.utf8); 을 추가해 준다.

VC++ 에서는 이상하게 wchar_t 로 선언된 "가나다" 란 문자열을 wcslen 으로 보면 길이가 6으로 나온다.

길이가 3으로 나오게 하려면
setlocale(LC_ALL,".949");
를 추가해 준다.

참고자료
[1] UTF-8  http://ko.wikipedia.org/wiki/UTF-8
[2] wchar_t http://www.killrain.net/channel/lab/?subject=C%2B%2B%EC%9D%98+%EB%8B%A4%EC%96%91%ED%95%9C+string+%ED%83%80%EC%9E%85&mode=view
[3] wprintf http://kldp.org/node/79934

1. VC++ 6.0

VC++ 6.0 (이하 VC++) 에서는 인라인 어셈블을 __asm 형식으로 사용한다.

여러개의 어셈블 명령어를 사용하려면 { } 로 묶어주면 된다.

2. GCC

GCC 에서는 asm( ); 형식을 사용하거나,
__asm__ ( ); 형식을 사용한다.

보통 __asm__ __volatile__ ( ); 형식으로 사용하라고 권한다.

GCC 는 (명령어 input output) 형태로 들어가게 된다.

자세한 사항은

http://www.ibiblio.org/gferg/ldp/GCC-Inline-Assembly-HOWTO.html 혹은
http://wiki.kldp.org/wiki.php/DocbookSgml/GCC_Inline_Assembly-KLDP

참고하면 된다.

3. 예제

변수의 overflow 를 체크하는 것을 만들어 보자.

변수가 overflow 가 발생했는지 알아보는 방법은 cpu의 flag 중 CF(carry flag) 가 세팅되어졌는지를 확인하는 것이다.
cpu flag 를 확인하는 방법은 어셈블을 사용할 수 밖에 없다.

flag 를 확인하는 방법 말고 다른 방법또한 존재하겠지만, 인라인 어셈블을 이용해서 확인을 해보도록 하자.
3.1 VC++

__asm {
      pushfd;          // eflags(32bit) 값을 스택에 넣는다.
      pop eax;        // eflags 값을 eax 에 넣는다.
      and eax,0x1;  //  0 bit (CF) 가 1인지 확인을 한다.
      mov i, eax;   // 그 값을 변수 i 에 넣는다.
}

이것을 매크로로 만들어도 된다.

#define CHECKOVERFLOW(x) __asm pushfd __asm pop eax __asm and eax,0x1 __asm mov x,eax

테스트 코드는 다음과 같다.
#include <stdio.h>
#define CHECKOVERFLOW(x) __asm pushfd __asm pop eax __asm and eax,0x1 __asm mov x,eax
int main(int argc, char* argv[])
{
 long l=0xFFFFFFF0;
 int sum;
 int flag;
 scanf("%d",&sum);
 l += sum;
 CHECKOVERFLOW(flag);
 if(flag){
  printf("over flow: %u\n",l);
 }
 else
  printf("not over flow:%u\n",l);
 
 return 0;
}

3.2 GCC

GCC 에서는 VC++ 과 다르게 C 내에서 선언한 변수를 쓸때에는 %0, %1 을 사용하여야 한다.
 asm ("mov %%eax, %0" :"=g"(flag));
여기서 %0 은 flag 란 변수를 뜻하는 말이다.

나머지는 VC++ 과 똑같다.

테스트 코드는 다음과 같다.
  1 #include <stdio.h>
  2
  3 int main()
  4 {
  5   long l=0xFFFFFFF0;
  6   int sum;
  7   int flag=0;
  8
  9   scanf("%d",&sum);
 10   l += sum;
 11
 12   asm ("pushf");
 13   asm ("pop %eax");
 14   asm ("and $0x1, %eax");
 15   asm ("mov %%eax, %0" :"=g"(flag));
 16
 17   if(flag)
 18     printf("overflow : %u\n",l);
 19   else
 20     printf("not overflow : %u\n",l);
 21   return 0;
 22 }



 

VC++ 에서는 win32 라이브러리인 winsock 을 사용할 수 있다.
MS 에서 제공하는 CSock 이란 놈도 역시 사용할 수 있다.

하지만 VC++ 에서 #include <winsock2.h> 만 하면 링크 에러가 난다.

그리하여 이를 해결하기 위해서는 옵션에서
ws2_32.lib 파일을 넣어주어야 한다.

그렇다면 어떻게?

VC++ 6.0 에서

Project->Settings

Project -> Settings or Alt+F7

사용자 삽입 이미지
Link Tab 에서 Object/library modules 란에 ws2_32.lib 을 적어준다


아니면 소스코드에
#pragma comment(lib,"ws2_32.lib") 를 추가해 준다.


그리고 컴파일하면 끝!
C/C++ 에서 변수를 선언하고 값을 넣으면, 메모리에 그 값이 저장된다.

C/C++ 에서는 typecast 를 허용한다.

int a = '\x78\x56\x34\x12';

라고 하면 메모리에 78 56 34 12 라고 들어가고 값을 읽어들일때는
12 34 56 78 로 읽어들일 것이다.

위 방식말고 16진수 12345678 의 10진수값을 바로 a 에 넣어도 메모리에는 같은 값이 들어간다.

이렇게 int 로 선언한후 char 포인터를 이용해 a 의 주소를 가리킨 다음 그 값을 읽으면
78 을 읽어 들인다.

만약 char a[4] = {'\x78','\x56',\x34',\x12'};
로 선언한후 int *p = (int *)a;
로 한후 *p 를 출력하게 되면 16진수 12345678 의 10진수 값이 나올것이다.

C/C++ 에서는 메모리에 저장되는 값은 16진수 형태로 같고 그걸 읽어들이는 자료형에 의해서 값을 읽는 방식이 정해진다.

float *p = (float *)a; 하게 되면, a 에 저장되어 있는값의 float 표현으로 출력될 것이다.

만약 **p 이면 a 에 저장되어 있는 4바이트내용의 주소에 있는 값을 읽어올것이다.
0x12345678 에 있는 값을 참조할 것이다.

이해가 되면, 자료형에 크게 상관없이 프로그램을 원하는데로 만들수 있을 것이다.

오로지 char 배열만 가지고 얼마든지 int , float, 포인터를 흉내낼수 있을것이다.

typecast 를 이용해서.
앞에서 int 형의 저장에 대해서 살펴보았다.

이제 부동소수 표현방식에 대해서 살펴보겠다.

부동소수 표현 방식은 4바이트의 경우

1 bit 부호 |  8bit 지수부 | 23 bit 가수부

로 나누어져 있다.

0|000 0000 0|000 0000 0000 0000 0000

로 표현되어 지는데.

지수부 표현방식은 2의 지수승으로 표현된다. 지수승 표현은 8bit 계산값 n 에서 127을 뺀값을 지수승 하는 방식이다.

예를 들어, 011 1111 1  일 경우 127이므로 127 - 127 =0 이 되므로 지수표현은 2의 0승 즉 1이 된다.

그다음 가수부 표현은 첫번째 bit 1/2
두번째 bit 1/4
이런식으로 계산하게 된다.

이 방식으로 1.0을 표현하면,

0|011 1111 1|000 0000 0000 0000 0000 0000 이 되고 이걸 16진수로 표현하면

3        f        8       0     0      0     0       0즉,
3f800000 가 된다.

실제 프로그램에서 메모리에는 위치가 반대로 되므로

00 00 80 3f  이런식으로 잡힐것이다.

같은 방식으로 1.5 는

3f c0 00 00 가 될 것이다.

사용자 삽입 이미지

C/C++ 에서 메모리가 어떻게 되고 변수가 메모리에 어떻게 자리잡는지 알아보도록 하자.

int main()
{
     int c = '\xde\xad\xbe\xef';
     int a = '\x78\x56\x34\x12';
     static int b;
     return 0;
}

다음과 같은 소스코드가 있을때, 변수  a 는 stack 에 자리잡게 된다. 4바이트 만큼.

a = '\x78\x56\x34\x12';

을 하게 되면, a 의 메모리는 78 56 34 12 순으로 저장되며 값을 읽어 올때는
12345678 로 읽어진다.

그리고 int a; int b; 로 하게 되면, b 다음에 a 가 바로 오는것이 아니라 dummy 중간에 dummy 값을 가진다.

사용자 삽입 이미지

78 56 34 12 다음에 dummy 값인 cc cc cc 가 들어있고 다음에 de ad be af 값이 들어있는것을 알 수 있다.

만약에 static int a; 하게 되면 a 값은 스택에 잡히지 않게 되고 data 영역에 잡힌다.

지역변수 a 는 0x0012FFxx 영역에 잡히지만,
static a 는 0x004171xx 영역에 잡힌다.

int 4 byte
char 1 byte
short 2 byte
double 8 byte
이다.

+ Recent posts