2011년 9월 10일 토요일

C언어 정수형 변수 int



C언어의 이해에 존재하는 문서에서 CPU 및 언어의 동작은 CPU와 컴파일 마다 틀리고 단지 여러가지 CPU를 고려 할 때 평균적인 개념들을 추출하고 도식한 것이다. 따라서 이 를 기반으로 직접 개발하고자 하는 CPU 및 컴파일러를 필요에 따라 분석하여 이해 하여야 한다. dolicom

C에서의 정수형 변수 int


정수형 변수 int C에서 가장 많이 사용하는 변수 이다. 그런데 이 기초적인 정수형 변수 역시 CPU에서 어떻게 표현되며 계산돼는지 알아야 정확히 이해 했다고 할 수 있다. 물론 포인터를 이해하는데 도움이 될 것이다.
우선 CPU의 일반적인 구조를 보자. 간단한 연산 구조 만을 보면 다음과 같다.



간단히 기능적 설명을 하면 다음과 같다.

1.  레지스터 : CPU 내의 데이터를 저장 한다. 이 레지스터 값은 CPU가 테이터 처리를 위한 기본적인 저장 공간이다. 메모리와 다른 점은 하드웨어 적으로 직접 ALU와 연결돼어 계산이 이루어 지며, 또한 메모리의 버퍼 역할을 한다. 메모리의 값을 레지스터로 옮겨 놓고 계산을 한 다음 다시 메모리로 갈 수 있다. 각 동작을 기계어 코드에의 실행에 의해 이루어 지며 위의 그림에는 생략 돼어 있다. 레지스터의 표기 방식은 각 CPU 마다 다른며, 개수와 길이 역시 다르다. CPU  몇비트인가를 결정하는 가장 중요한 길이가 레지스터 이다.
CPU는 각각 다른 구조와 길이가 결정 돼어 있기 때문에 자기가 사용하는(개발하는) CPU의 구조를 이해해야 만 정확한 C의 프로그램이 가능 하다. 8bits/16bits CPU의 어떤 것들은 경우에 따라 2개 혹은 4개의 레지스터가 결합 돼어 사용 하기도 한다. 8bits CPU에서 16bits 정수형을 지원하는 것도 이것을 이용할 수 있다.

2.       ALU : ALU CPU의 계산을 위한 모듈이다. 사칙 연산이 주로 이루어 지며, 기타 bit 연산등의 계산이 이루어 진다. 기계어 명령 하나로 레지스터 값이 연결 돼어 계산이 이루어 진다. 따라서 C의 정수 연산을 이 레지스터와 ALU을 사용하여 계산 된다. 몇가지 CPU마다 다른점은 CPU의 구조를 이해해야 한다. CPU가 고급일수록 4칙연산중 곱셈과 나눗셈을 지원하고 낮은 레벨의 CPU는 이원하지 않는 것이 일반적이다. 곱셈과 나눗셈이 지원돼지 않는 CPU는 컴파일러가 여러 개의 기계어 명령을 프로그램하여 라이브러리와 지원 한다.

3.       FLAG : 이것은 ALU에서 연산될 때, 연산 결과로 나타내는 값을 저장한다. 예를 C carry로 이진수 연산 결과 carry값이 저장 돼어 유지 된다. FLAG값을 가지고 branch jump 명령으로 C if 문을 구성 한다. 다음과 같은 프로그램에서 사용돼는 과정을 살펴 보자.

int a;
if (a == 10)
   a++;
여기서 비교 과정을 보면 다음과 같다. 여기서 컴파일러가 정수형 변수 a는 레지스터 R0를 사용하는 것으로 결정 돼어 있다면

    CMP R0, #10                    -- R0 - 10
    JP   NZ, SKIP_ADDRESS   -- Z=0(0이 아니면) 이면 jump
    INC  R0                           --  R0 값을 1 증가
          SKIP_ADDRESS:
            
     여기서 CMP 명령은 연산 명령으로 결과값은 버리지만 FLAG 값을 설정 한다.
     만약 R0 10이면
        R0-10 = 0
     연산 값은 0으로 Z(Zero) 1로 설정 된다.
     JP  NZ, addr Z flag 0으로 설정 돼어 있다면 다음 addr 값으로 jump하라는 기계어 코드이다.  결국 연산 명령과 FLAG을 사용한 jump명령으로 if 문을 구성할 수 있고 FLAG의 역할을 이해 할 수 있을 것이다.

4.       MUX A,B는 어느 레지스터를 연산에 사용할것가를 결정한다. 이는 기계어에 명시 돼어 있다.

5.       operand 는 메모리에서 가져온 값을 직접 계산 할 때 사용돼어지는 내부 레지스터 이다.
예를 들어
   int a;
   a += 10;
이 변수 a가 연산 될 때는 CPU의 어느 레지스터에 컴파일러의 규칙에 의해 정의 돼어 있고 연산이 이루어 진다. 여기서 10은 값은 기계어의 코드 다음에 붙어 있는 값이다.
컴파일러에 따라 다르지만 다음과 같은 코드로 위의 프로그램이 코딩 된다.
   ADD R0, #10     R0+10 -> R0 - CPU마다 틀리나 구조는 같다.
위와 같은 어드레싱 모드를 우리는 immediately 모드라고 하고, 동작할 때 10은 임시 저장 공간인 operand에 저장 된다.
이 때의 10의 값은 프로그램  변수 영역의 메모리가 아니고 프로그램 코드 부분에 붙어 동작 한다.

6.       ALU을 통해서 계산 된 값을 다시 mux을 통해 레지스터로 저장 된다.

위의 CPU의 기본구조 속에서 정수형 변수 int는 당연히 직접적으로 관련되어 컴파일 된다.
정수형 변수 1개가 정의돼면 연산시 컴파일러가 레지스터 지정하고 이 레지스터 값을 이용 ALU를 통해 직접 연산 됨으로써 동작 한다.
그런데 정의 된 정수형 변수와 레지스터는 어떤 관계가 있는가? 다음 프로그램에서 보면

int a;
int b,c;
void main()
{
   a = 10;
b = 10;
   c = a+b;
}
변수 a,b,c는 변수 영역의 공간에 활당된다. 물론 값이 변해야 하기 때문에 RAM 설정 된다. 프로그림 ‘a =10;’의 실행 과정은 다음과 같다.
만약 a의 주소 값이 0x30001104이라면

   MOV R0,#10           - Immediately address mode라 함.
   MOV 3001104H,R0   - 직접 주소 모드(direct address mode) 라 함.

이 때의 주소값은 프로그램의 기계어 코드에 붙어 있는 형태가 되고 이 주소에다 10을 옮기는 과정이 이루어 진다. 변수 b 역시 마찬가지 과정을 걷힌다.
다음은 ‘c=a+b’는 다음과 같다.

   MOV R0, 3001104H    0x30001104 번지의 값 -> R0
   ADD R0, 3001108H     R0 + 0x30001108 번지의 값 -> R0
   MOV 300110CH,R0    R0 -> (0x3000110C) 변수 c의 주소값에 저장.

그런데 변수가 글로벌로 설정되면 위와 같은 과정을 걷히지만 지역변수라면 주로 상황이 다르다. 지역변수는 주로 stack 영역에 저장 되며 stack을 기준으로 하는 주소 방식이 사용 된다. 물론 컴파일마다 차이가 나고 레지스터 숫자에 따라 차이는 있다. 다음과 같은 코드의 예가 이 것에 해당 된다.

int add(int a, int b)
{
int c;
   c = a+b;
return c;
}
위의 프로그램에서 정수형 변수 c stack을 사용하여 할당 한다. 물론 호출 할 때 변수 a,b역시 stack을 사용 할당 한다. 특히하게도 어떤 컴파일러는 지역변수를 직젖 레지스터로 지정하는 컴파일러도 있다. 따라서 이를 정확히 확인 하려면 컴파일러 옵션중에 assembler코드를 생성해 주는 것이 있는데 이 를 활용하면 확인 할 수 있다. 물론 이렇게 하려면 레지스터가 많은 경우나 가능 하다. CPU에 따라 레지스터 A (Accumulator)만 있는 것은 불가능 하다.

위의 과정을 보면 CPU의 레지스터와  정수형 변수와의 관계를 어느 정도는 짐작할 수 있을 것이다. 그런데 float point 변수는 직접 CPU가 지원하지 않기 때문에 레지스터 직접 사용할 수 없고 여러가지 방식에 의해 실행 된다. 단 일반적인 DPS CPU FLOAT POINT 를 레지스터 및 연산 모듈에서 지원 하기 때문에 정수형 처럼 사용할 수 있어 무지 빠르게 연산할 수 있다.

프로그램 입장에서 int의 특징은

1.       정수 값을 저장하는 변수이다. 표현 방식이 2의 보수 체계를 사용한다. 따라서 음수를 나타내는 값이 1개가 더 많다.

short int a;
  a = 2;
  a += -3; -> 이 때 +는 CPU의 ALU에서 계산 된다. 이 과정을 2진수 표현하면 다음과 같다.

       16 bits 정수형의 경우 2의 보수 체계

32767     0111 1111 1111 1111
32766     0111 1111 1111 1110
                  
1     0000 0000 0000 0001
0     0000 0000 0000 0000
-1     1111 1111 1111 1111
-2     1111 1111 1111 1110
-32768    1000 0000 0000 0000

위의 값이 ALU을 통해 계산이 이루어 진다.
    a = 2; a += -3; 의 경우
    MOV R0,#2
    ADD R0,#-3


R0   2  :      0000 0000 0000 0010
op  -3  :      1111 1111 1111 1101
   -1    :[1]  1111 1111 1111 1111
   여기서 C[1] carry가 발생 하여 C=1, Z = 0이다.

    2.  정수를 나타내는 값의 변위는 CPU 마다 다르다. 8bits cpu의 경우 대개 2개의 레지스터를 결합하여 사용하여 16bits를 지원 한다. CPU의 레지스터 구조와 깊은 상관 관계가 있다. 물론 컴파일러와도 관계가 있다.
     -         Z80 (8bits) - 16bits
int a;  -  레지스터 BC로 할당.  
            필요에 따라 DE, HL 사용

     - 68000계열  - 32bits
     - 80x86       - 32bits

int 변수 길이

정수형은 cpu 마다 다른 길이의 비트를 사용하므로 일반적으로 많이 사용하는 형태를 본다.
8 비트 CPU의 대부분은 int 변수가 16비트(2바이트)의 길이를 갖는다. 따라서 int을 했다면 +-32,xxx로 숫자가 재한됨을 알고 프로그램 하여야 한다. 보통 8비트 CPU의 ALU는 한번의 기계어 명령에 8비트 계산 밖에 못하므로 int(16비트)형을 계산할 때는 2번 이상의 기계어 코드에 의해 계산이 이루어 진다. 따라서 CPU의 비트수가 낮으면 같은 CPU의 클럭을 사용하더라도 속도가 느릴 수 밖에 없다.

8051, AVR8, Z80, SAM8 ,...
 변수                          숫자범위 (10진수)                숫자범위 (16진수)          의미
 char  cnt;                        -128 ~ +128                       0x80 ~ 0x7F              8비트 부호가 있는 정수
 int cnt;                        -32,768 ~ 32,767                  0x8000 ~ 0x7FFF          16비트 부호가 있는 정수
 long int cnt;       -2,147,483,648 ~ 2,147,483,647   0x80000000 ~ 0x7FFFFFFF   32비트의 부호가 있는 정수

 변수                               숫자범위 (10진수)         숫자범위 (16진수)            의미
unsigned  char  cnt;          0 ~ 255                        0x00   ~ 0xFF                 8비트 부호가 없는 정수
unsigned  int cnt;              0 ~ 65,535                    0x0000 ~ 0xFFFF            16비트 부호가 없는 정수
unsigned long int cnt;        0 ~ 4,294,967,295       0x00000000 ~ 0xFFFFFFFF   32비트의 부호가 없는 정수


80x386, ARM
 변수                                 숫자범위 (10진수)                        숫자범위 (16진수)            의미
 char   cnt;                             -128 ~ +128                                  0x80 ~ 0x7F              8비트 부호가 있는 정수
 short int cnt;                      -32,768 ~ 32,767                             0x8000 ~ 0x7FFF          16비트 부호가 있는 정수
 int cnt;                    -2,147,483,648 ~ 2,147,483,647              0x80000000 ~ 0x7FFFFFFF   32비트의 부호가 있는 정수
 long int cnt;             -2,147,483,648 ~ 2,147,483,647              0x80000000 ~ 0x7FFFFFFF   32비트의 부호가 있는 정수
 __int64   cnt;  -9223372036854775808 ~ 9223372036854775807 0x8000000000000000 ~ 0x7FFFFFFFFFFFFFFF
      ->  __int64  : 64비트 정수형 변수 (Visual C++, 80x86계열에서 사용, 다른 CPU와 컴파일러라면 이 형태를 찾아야 함.)

 변수                               숫자범위 (10진수)                             숫자범위 (16진수)             의미
unsigned  char   cnt;         0 ~ 255                                                0x00 ~ 0xFF                8비트 부호가 없는 정수
unsigned  short int cnt;      0 ~ 65,535                                         0x0000 ~ 0xFFFF            16비트 부호가 없는 정수
unsigned   int cnt;             0 ~ 65,535                                         0x0000 ~ 0xFFFF            32비트의 부호가 없는 정수
unsigned   long int cnt;      0 ~ 4,294,967,295                          0x00000000 ~ 0xFFFFFFFF     32비트의 부호가 없는 정수
unsigned   __int64   cnt;     0 ~ 18446744073709551615  0x0000000000000000 ~ 0xFFFFFFFFFFFFFFFF
                                                                                                                              64비트 정수형 변수 (Visual C++)



정수형 변수 int의 메모리 활당

C에서 변수를 할당하는 방식은 크게 글로벌 변수냐 지역변수냐에 따라 다르다.

글로벌 변수는 함수 밖에 선언 돼는 변수가 대표적이며, 지역변수는 함수내에 선언되는 경우 이다. 이전에 언급 했드시 글로벌 변수는 프로그램이 동작 할 때, 메모리에 변수 영역의 메모리에 할당 돼기 때문에 프로그램이 로드 될 때 결정 된다. 대신 지역 변수는 stack 영역에 할당돼기 때문에 함수가 실행 될 때 생성되고 끝나면 사라 진다.





위의 표는 일반적인 프로그램 구조 이다. 이것 역시 컴파일러 마다 용어가 다르고 더 세분화 된다. 중요한 영역 만을 표시 했다.   ‘bss, data, text’ gcc linker의 표시 방법이다.

1.       Data 초기 변수 영역 : 이것은 초기 값을 갖는 변수 영역이다. CPU 직접 설계하여 프로그램 하다보면 start code에 이 영역을 설정하는 프로그램 코드가 보일 것이다.

만약 프로그램이 다음과 같다면
 
   int a = 10;
   int b;
   void main()
   {
  b = 20;
  b += a;
  
}

위에서 ‘int a = 10;’은 프로그램 코드에 의해 a에 값이 설정 돼지 않고 컴파일러가 10 ROM 영역에 놓고 처음 로드 시 ROM 영역의 값을 표의 초기값 변수 영역에 옮긴다. 그런데 변수 b의 경우는 직접 프로그램 코드에 의해 20인 설정 된다.
CPU를 설계하여 OS 없이 프로그램 할 때는 주의해야 한다. 초기 변수값의 복사는 보통 C프로그램을 붙일 경우 startup code assember compiler가 제공하는 코드를 분석하면 이해 할 수 있다. 귀찮으면 초기 변수를 사용하지 않으면 된다.

2.       BSS  영역 : 이 영역은 초기 값이 없는 변수 영역 이다. 따라서 경우에 따라 초기값이 어떤 값이 설정 될 지 모른다. 위의 프로그램에서 변수 'int b;'경우 여기에 할당 된다.
3.       Code 영역 : 프로그램 코드가 있는 곳이다.
4.       Heap 영역 : 프로그램에서 malloc함수나 new  연산자에 의해 필요에 따라 변수 영역이 설정 될 수 있는 변수 영역 이다.
5.       Stack 영역 : 프로그램이 실행 돼면서 사용되는 stack 영역이다.
 
위의 표는 가장 필수적이고 기초적인 section만을 언급 했을 뿐 컴파일 마다 다르므로 컴파일러을 입수 하면 분석하여 CPU 의 주소에 맞추어 설정 해야 한다. 이 부분이 정의는 보통 linker에 의해 사용되며 파일의 형태로 설정 된다.
또한 위의 section의 모든 요소는 모든 프로세서가 생성 할 때 마다 OS에 의해 리소스를 확보하고 실행 된다. CPU를 직접 설계한 경우는 물론 OS 없이 프로그램하는 경우도 있으므로 정당한 ROM,RAM 메모리에 할당하고 실행 시키면 된다.

이것은 차후에 정리할 계획임. CPU등의 이해와도 연결되는 문제이고 임베디드 등..
이정도 하고 넘어감.
크리에이티브 커먼즈 라이선스

댓글 없음:

댓글 쓰기