|
연산자 |
내용 |
예 |
|
++ |
1증가 |
a++; /* a=a+1과 동일*/ |
|
-- |
1감소 |
a--; /* a=a-1과 동일*/ |
|
?: |
판단 |
y=(a<0) ? a : -a; /* a가 0보다 크면 y=a, 0보다 작으면 y=-a */ |
|
sizeof |
데이타형의 크기 |
y=sizeof(int); /*정수형(int)는 2바이트 크기이므로, y는 2가된다*/ |
비트단위 논리연산자
일반적인 변수는 모두 바이트단위(혹은 워드단위)로 처리되지만, 내장형제어기에서는 비트단위로 변수를 조작하는 경우가 많다. 비트단위 논리연산자는 해당변수에 대해 비트단위로 연산을 수행한다.
|
연산자 |
내용 |
예 |
|
& |
비트단위 논리곱(AND) |
y = a&0x0F;
/*a의 하위 4비트만 얻는다.*/ |
|
| |
비트단위 논리합(OR) |
y = a|0x0F
/*a의 하위 4비트를 모두 1로 만든다*/ |
|
~ |
비트단위 부정(1의 보수) |
y = ~0x00; /*y를 0xFF로 만든다*/ |
|
^ |
비트단위 배타적 OR |
y = a^0xFF;
/*a중 해당비트가 1이면 0으로 만든다*/; |
|
<< |
우측으로 이동 |
y = 0x1<<2; /*0x1을 우측으로 2비트 이동, y=0x4가 된다*/ |
|
>> |
좌측으로 이동 |
y = 0x2>>1; /*0x2를 좌측으로 1비트 이동,y=0x1이 된다*/ |
if문
가장 기본적인 선택문으로 판단결과가 참인가 거짓인가에 따라 실행해야 할 문장을 선택한다. if결과 처리해야 할 문장이 단일 문장이라면 중괄호 {, } 가 필요 없지만 2개이상의 문자를 연속으로 처리해야 한다면 중괄호를 사용하여 블록화 해야 한다
if(x>100) printf(x는 100보다 크다);
else printf(x는 100 보다 작거나 같다“);
if(x>=10)
{
printf(x는 10과 같다“);
printf(x는 10보다 크다“);
}
switch/case 문
비교결과에 따라 실행해야 할 코드의 종류가 여러가지일 경우 사용한다. 비교결과와 일치하는 case에 해당되는 문장을 break 키워드를 만날 때까지 실행한다. switch로 선택하고자 하는 변수는 반드시 정수형(char, int, long 등)이어야 한다.
char ch;
switch(ch) //ch=문자형 정수
{
case 'a': printf(ch는 a입니다“);
case 'b': /* ch가 'b'일 경우 이곳으로 진입하지만 break문이 없으므로 아래의
case 'c': printf(ch는 b 혹은 c입니다“);
default : printf(ch는 그밖의 문자입니다“);
break;
}
for 문
처음 초기값을 설정한 다음 조건이 참인 동안 변화치를 수행하면서 문장을 실행한다
/* i=0으로 초기값을 설정한 후,// sum=sum+i를 실행한다. 그후 i++ (즉,i=i+1)를 처리하고,
비교문 (i<10)의 결과가 참일 때까지 sum=sum+i와 i++ 처리를 반복한다*/
sum=0;
for(i=0;i<10;i++) sum=sum+i;
printf(0에서 9까지의 합은 %d,sum);
while 문
조건식이 참일 동안 while에 따르는 문장을 실행한다
sum=i=0; /* sum=0; I=0;과 같다. */
while(i<10) /* i가 10보다 작은 경우 수행한다 */
{
sum=sum+i;
i++; /* i=i+1과 같다 */
}
printf(0에서 9까지의 합은 %d, sum);
do{ 문장 }while( 조건문 );
우선 do내부의 문장을 실행한 후 while 다음의 조건문이 참인 동안 do 문장을 실행한다. 최소 1번은 실행하는 while문이라고 볼 수 있다.
sum=i=0; /* sum=0; i=0;을 이렇게 쓸 수 있다 */
do
printf(0에서 9까지의 합은 %d, sum);
C언어에서의 함수는 수학에서 이야기하는 함수와는 다른 의미를 가지고 있다. C언어의 함수는 특정한 작업을 수행하도록 설계된 독립적인 프로그램을 말하는 것이니 다른 언어의 서브루틴과 같은 동작을 한다고 보면 된다. 프로그램에서 특정작업을 반복해서 수행해야 할 경우, 그 작업에 해당되는 적절한 함수를 만들어서 필요할 때마다 호출하면 프로그램을 간결하면서도 이해하기 쉽게 구성할 수 있다.
C언어에서는 모든 프로그램이 갖는 3가지 구성인 입력, 처리, 출력 부분 중에서 입력과 출력부분은 과감히 삭제하고 오로지 처리부분만으로 구성되어 있다. 입력과 출력이 필요하지 않는 프로그램은 거의 없으므로, C언어 본체만으로 원하는 일을 작성하기는 힘들게 된다. 따라서 입력과 출력부분에 해당되는 함수부터 제작해야 한다.
사실 처리부분을 제외한 모든 부분을 사용자가 준비한다는 것은 지극히 어려운 일이지만 컴파일러 제조회사에서 표준적인 런타임 라이브러리를 준비하여 입출력 부분은 물론 다양한 처리함수까지 준비하고 있다. 사용자는 이런 라이브러리를 적절히 활용하여 자신의 용도에 맞는 함수를 제작하면 된다.
런타임 라이브러리
런타임 라이브러리는 그것이 선언된 헤더파일만 소스코드에 포함시키면 간단히 사용할 수 있다. 예를 들어 자주 사용하는 출력함수인 printf()는 그것이 선언된 stdio.h 파일만 포함시키면 그 기능을 맘껏 활용할 수 있다. 런타임 라이브러리의 자세한 내용은 터보-C 관련서적을 참고하기 바란다.
#include <stdio.h> /* printf(), scanf() 등의 함수가 선언되어 있다.*/
int main(void)
사용자 정의 함수
런타임 라이브러리에 포함된 함수만으로 원하는 프로그램을 작성하기는 어려우므로 자신의 용도에 알맞은 함수는 스스로 작성하거나 다른 사람이 작성한 함수를 참조하여 자신의 용도에 맞게 응용해야 한다. 다음은 1에서 n까지의 정수를 더해주는 summing()함수를 작성한 예이다.
/* 1에서 n까지의 정수를 더하여 그 결과를 되돌려 주는 함수 */
int summing(int n)
{ /*함수 블록 시작 “{”로 시작한다*/
int i,sum=0; /* 내부변수 선언, sum은 선언되면서 0으로 초기화된다 */
for(i=1;i<=n;i++) sum=sum+i;
return(sum); /* 결과 값 전달 */
} /* 함수 끝 “}”로 끝난다 */
int main(void) /* 매개변수가 없는 함수는 void 인자형으로 선언한다 */
선행처리기란 프로그램이 컴파일 되기 전에 처리되는 명령어를 말한다. 여러가지가 있지만 주로 사용되는 것은 #include와 #define 두 가지이다
#define
보통 매크로(macro)라 말한다. 매크로란 일련의 문자, 상수, 수식을 하나의 식별자(문자열)로 표시하는 것으로, 정의된 후 식별자만 기술하면 컴파일 과정에서 지정한 수나 수식으로 전개된다. 매크로를 이용하면 의미전달이 어려운 숫자나 수식을 이해하기 쉬운 문자로 표기할 수 있으므로 편리하다. #define 다음에 변수의 규정에 맞는 문자열을 사용하고 대체할 문자나 숫자를 기술한다. 매크로 정의 다음에는 ; 을 사용하지 않는다.
#define PI 3.14 /* 3.14대신 PI를 사용한다 */
#define LCD_ON 0xF7
#define LCD_addr 0x100 /* 0x100를 LCD_addr로 표기한다 */
#define outLCD(data) outportb(LCD_addr,data)
unsigned char Pdata;
float angle;
Pdata = LCD_ON;
outLCD(Pdata); /* outportb(0x100,0xF7); */
angle = 2.*PI;
#include
현재의 자리에 다른 파일을 포함하기 위해 사용한다. #include 다음에 포함하고자 하는 파일이름을 적는다. 파일이름을 감싸는 데는 < >와 “ ”를 사용하는데, 이 둘은 약간 다른 의미를 가진다. < >부호는 표준 시스템 디렉토리에서 파일을 찾고, 부호는 그 파일이 있는 디렉토리에서 파일을 탐색한다. 보통 내장 함수들이 선언된 시스템에서 제공하는 헤더 파일들은 < >로 포함시키고, 사용자가 자체적으로 제작한 헤더파일은 로 포함을 시킨다.
#include <stdio.h> /* 시스템 디렉토리에서 stdio.h 파일을 읽어 포함시킨다 */
#include head.h /* 현재의 디렉토리에서 head.h 파일을 읽어 포함시킨다 */
#include c:\mydir\head.h /* c:\mydir 디렉토리에서 head.h 파일을 읽어 포함시킨다*/
typedef
typedef문은 프로그래머 자신만의 자료형을 만들어 준다. #define과 기능이 비슷하지만, 외형적으로 typedef는 자료형의 변환에만 사용할 수 있다. typedef는 자주 사용되는 자료형을 알기 쉽게 표현하게 하고, 다른 기계에 프로그램의 이식을 쉽게 하도록 도와준다. typedef 다음 원래의 자료형과 바꾸고자하는 자료형을 기술하여 사용한다. #define과 달리 #으로 시작하지 않고, 끝부분에 ;를 사용한다는 것에 주의한다.
typedef unsigned char BYTE; /* unsigned char형을 BYTE로 바꾼다 */
BYTE MSB; /* unsigned char MSB; 와 동일한 의미를 갖는다 */
C언어에서 struct(구조체)와 union(공용체) 키워드를 이용하여 다섯가지 기본 데이타 타입(char, int, float, double, void)이 조합된 새로운 데이타 타입을 만들 수 있다. struct는 하나의 변수명이 기억장소가 다른 여러 개의 변수집합을 대신해주며, union은 2개 이상의 변수가 동일한 기억장소(메모리)를 공유하게 한다.
struct
struct는 다음과 같이 정의한다
struct 구조체명{
항목1;
항목2;
:
}구조체변수명;
앞서 정의된 구조체형의 새로운 구조체변수는 다음과 같이 정의한다.
struct 구조체명 구조체변수명;
구조체변수명 바로뒤에 '.'을 첨가한 후 항목명을 연이어 기록하면 외부에서 접근할 수 있는 변수명이 된다. 이때 사용하는 '.'을 항목연산자(membership operator)라고 한다. 구조체형 포인터변수는 '->'라는 기호로 항목을 표시한다.
struct SPOINT{
}sp1; /* x,y라는 2개의 데이타타입으로 구성된 SPOINT라는 struct (구조체) 와 그 구조체를 사용한 변수 sp1을 선언한다. 여기서 x,y는 모두 다른 메모리번지를 가지게 된다. SPOINT 구조체의 크기는 2바이트 (x)+1바이트(y)=3바이트가 된다. */
struct SPOINT sp2; /* 앞서 정의한 SPOINT라는 구조체형 변수 sp2를 선언한다*/
sp1.x = 10; /* sp1 구조체변수의 x항목에 10을 대입한다. sp1.y에는 영향을 주지 않는다. */
sp2.y = 20; /* sp2 구조체변수의 y항목에 20을 대입한다. sp2.x에는 영향을 미치지 않는다.*/
union
union(공용체)는 struct(구조체)와 비슷하게 정의한다.
앞서 정의된 공용체형의 새로운 공용체변수는 다음과 같이 정의한다.
공용체 항목을 지시하기 위해서는 공용체변수명 바로뒤에 ‘.’을 첨가한 후 항목명을 연이어 기록하면 된다.
union UPOINT{
int x; /* 2바이트 크기의 항목 */
char y; /* 1바이트 크기의 항목 */
}up1; /* x,y라는 2개의 데이타타입으로 구성된 UPOINT라는 union (공용체)와 그 공용체를 사용한 변수 up1을 선언한다. 여기서 x,y는 같은 메모리 번지를 가지게 된다. 따라서 up1.x를 변화시키면 up1.y 부
분도 up1.x가 차지하는 메모리 공간만큼 변하는 효과를 가진다. UPOINT 공용체의 크기는 가장 크기가 큰 항목의 크기가 되므로 이 경우 2바이트(x의 크기)가 된다.*/
union UPOINT up2; /* 앞서 정의한 UPOINT라는 공용체형 변수 up2를 선언한다*/
up1.x = 0x1234; /* up1 공용체변수의 x항목에 0x1234를 대입한다. up1.y는
up1.x와 같은 메모리 번지를 공유하고, 1바이트의 크기
이므로 up1.x의 하위 1바이트인 0x34가 된다.*/
up2.y = 0x56; /* up2 공용체변수의 y항목에 0x34를 대입한다. up2.x는 up2.y와 동일한 메모리 번지를 공유하고, 2바이트의 크기이므로 up2.x의 하위 1바이트는 0x56이 된다. up2.x의 상위 바이트는 원래의 값과 동일하다.*/
배열
배열은 똑같은 자료형을 갖는 원소(변수)의 집합을 말한다. 원소의 번호는 1에서 시작하지 않고, 0부터 시작한다. C 컴파일러는 배열의 크기를 검사하지 않으므로, 배열의 크기 이상의 데이타에 접근하면 예기치 않은 에러가 발생하므로 주의해야 한다.
int aa[10]; /* aa[0],aa[1],aa[2]... aa[9] */
float ff[5]; /* ff[0], ff[1],ff[2],ff[3],ff[4] */
aa[9]= 111; /*정상적인 동작*/
aa[20]= 123; /* aa[10]이상은 쓰기 금지, 배열 aa의 크기는 10이므로 허가된 원소번호는 aa[0]부터 aa[9]까지다. 배열의 크기를 넘어선 aa[20]에는 다른 데이타 혹은 실행코드가 쓰여있을 수도 있으므로 동작시 예기치 않는 에러가 발생한다*/
포인터
일반적인 변수를 다룰 때, 사용자는 변수의 내용에만 관심을 가지면 되고, 그 변수가 차지하는 메모리의 번지를 의식할 필요는 없다. C언어에서 변수가 정의되면 그 변수에 읽고 쓰여지는 데이타를 보존하기 위해 메모리의 특정번지에 변수가 위치하게 된다. 에러 없이 작성된 프로그램에 대해 컴파일러는 변수나 실행코드와 충돌하지 않도록 적절한 메모리 번지에 변수를 할당하므로 사용자는 변수가 위치한 메모리의 위치에 관심을 가지지 않아도 된다.
마이크로프로세서를 이용한 내장형제어기(Embedded Controller)는 비교적 적은 양의 메모리 위에 실행 코드와 데이타를 담게 되므로 풍부한 메모리를 갖는 IBM-PC 환경에 비해 변수와 메모리의 관계에 대해 섬세한 접근이 필요하다.
일반변수는 조작할 대상이 변수의 값인 반면, 포인터는 다루는 대상이 메모리 번지라는 점이 특이하다. 포인터 관련 연산자는 *과 &가 있다. *는 포인터변수를 선언하거나 포인터변수의 내용에 접근할 때 사용하고 &는 일반변수의 메모리번지를 알아낼 때 사용한다.
char ch; /* ch는 문자형 변수 (크기는 1바이트=8비트) */
int i; /* i는 정수형 변수 (크기는 2바이트=16비트) */
int *pch; /* *pch는 문자형 변수에 대한 포인터 */
int *pi; /* *pi는 정수형 변수에 대한 포인터 */
.
ch= 0x12; /* 문자형 변수 ch에 1바이트의 데이타 0x12를 기록한다 */
pch = &ch; /* 문자형 변수 ch의 메모리 번지를 pch에 대입한다.*/
pch=0x1000; /* pch가 메모리 번지 0x1000 (16진수)를 지시하게 한다 */
ch = *pch; /* pch가 지시하는 메모리(0x1000번지)에서 1바이트(8비트)데이타를 꺼내어 ch에 대입한다. *pch연산 결과가 몇 바이트의 데이타를 꺼낼 것인가 하는 결정은 pch를 정의할 때 사용한 데이타 타입에 의해 결정된다. 앞의 pch정의에서 char *pch; 와 같이 *pch의 데이타 타입을 char(1바이트)로 정의했으므로 *pch에서 얻어지는 데이타는 1바이트가 된다. */
i= 0x1234; /* 정수형 변수에 2바이트의 데이타(0x1234)를 기록한다 */
pi= &i; /* pi에 정수형 변수 i의 메모리 번지를 넣는다 */
pi=0x2000; /* pi가 메모리 번지 0x1000 (16진수)를 지시하게 한다 */
i = *pi; /* pi가 지시하는 메모리(0x1000번지)에서 2바이트(16비트)데이타를 꺼내어 i에 대입한다. *pi 연산 결과가 몇 바이트의 데이타를 꺼낼 것인가 하는 결정은 pi를 정의할 때 사용한 데이타 타입에 의해 결정된다. 앞의 pi정의에서 int *pi; 와 같이 *pi 데이타 타입을 int(2바이트)로 정의했으므로 *pi 연산으로 얻어지는 데이타는 2바이트가 된다. */
포인터는 메모리의 어드레스 버스라 생각할 수 있다. 포인터 연산자 (*)는 포인터가 지시하는 어드레스 번지에 해당되는 데이타 값을 얻는 기능이라 생각할 수 있다. 동일한 어드레스를 지정해도 8비트 폭의 메모리에서는 8비트 데이타가, 16비트 폭의 어드레스에서는 16비트 데이타를 얻을 수 있듯이, 문자형 포인터 (char *pch)에 대한 포인터 연산 (*pch)의 결과는 8비트(1바이트)값이 출력되고, 정수형 포인터( int *pi)에 대한 포인터 연산 (*pi)의 결과는 16비트(2바이트) 값이 출력된다.
터보-C의 near 와 far 포인터
V25, 386EX는 8086과 소프트웨어적으로 호환되는 칩이므로 메모리를 80x86과 마찬가지로 세그먼트(16비트):옵셋(16비트)의 형태로 다루게 된다. 세그먼트와 옵셋의 값을 자유롭게 바꿔 가면서 접근할 수 있는 메모리 공간은 총 1메가 바이트이고, 세그먼트가 정해져 있을 때 옵셋의 값만 변화시켜가면서 다룰 수 있는 메모리 공간은 64K 바이트가 된다.
터보-C를 스몰모델(small model)로 컴파일 했을 때, 데이타의 번지는 DS(데이타 세그먼트)를 기준으로 옵셋만 변화하게 되어 64K 바이트의 메모리 공간에 할당된다. DS의 값은 프로그램을 메모리에 이식할 때 결정한다. 스몰모델에서도 DS의 값에 의존하지 않고 1메가 바이트 메모리 전체 영역을 다뤄야 할 때가 있다. 이와 같이 64K 혹은 1메가 바이트의 메모리를 다룰 수 있도록 하기 위해 터보-C에서는 포인터 형을 정의하는 키워드로 near와 far를 준비하고 있다.
near 포인터는 64KB용, far 포인터는 1메가 바이트 용으로 다음과 같이 사용한다.
int far *fip; /* fip는 1메가 바이트 메모리 영역용(far) 포인터 */
int near *nip; /* nip는 64KB 메모리 영역용(near) 포인터 */
터보-C 스몰모델에서는 코드와 데이타의 기본형을 모두 near형으로 가정하기 때문에, 스몰모델에서 64KB의 메모리 영역을 다룰 때 near라는 키워드는 사용하지 않아도 무방하다. far형 포인터에 메모리번지를 대입할 때는 long형 정수를 사용한다. long형 정수(4바이트)중 앞의 2바이트는 세그먼트, 뒤쪽의 2바이트는 옵셋을 나타낸다.
int i; /* 정수형 (2바이트) 변수 i*/
int far *fip; /* fip는 1메가 바이트 메모리 영역용(far) 포인터 */
int near *nip; /* nip는 64KB 메모리 영역용(near) 포인터, 스몰모델에서 int *nip와 동일한 역할을 한다*/
nip=0x1000; /* nip는 DS(데이타 세그먼트)를 기준으로 한 0x1000의 옵셋번지(DS:0x1000)를 나타낸다. => 64KB 메모리 영역*/
fip=0x20001234L; /*fip는 2바이트 세그먼트(0x2000)와 2바이트 옵셋(0x1234)의 메모리 번지(0x2000:0x1234)를 나타낸다=>1메가 바이트 메모리 영역*/
*nip=0x5555; /* nip의 메모리 번지( DS:0x1000 번지)에 0x5555의 데이타를 기록한다*/
i = *nip; /* nip의 메모리 번지(DS:0x1000 번지)의 내용(=0x5555)를 i에 대입한다*/
*fip=i; /* i의 내용(=0x5555)를 fip의 메모리 번지(0x2000:0x1234)에 기록한다*/
배열과 포인터의 관계
배열(array)의 이름은 첫번째 원소의 주소를 나타낸다.
로 정의했을 때, &aa[0] (즉 aa[0]의 메모리 번지)와 aa는 같은 값이 된다.
int i;
int *pi;
int aa[10];
pi=aa; /* pi에 aa배열 첫번째 원소의 메모리 번지를 대입
이것은 pi=&aa[0]과 동일하다 */
*pi=0x1234; /* 이것은 aa[0]=0x1234와 동일하다 */
*(pi+1)=0x3456; /* 이것은 aa[1]=0x3456과 동일하다 */
지역변수는 그것을 선언한 함수 내부에서만 인식되는 변수이다. 이와 대조적으로 전역변수는 함수외부에서 선언된 변수로 그것을 선언하지 않은 함수 내부에서도 인식되는 변수를 말한다. 만약 어떤 함수에서 전역변수와 같은 이름으로 지역변수를 선언할 경우 그 함수 내에서는 지역변수가 우선한다. 즉 전역변수와 지역변수의 이름이 같은 경우 전역변수는 무시되고 지역변수만이 영향을 발휘한다. 전역변수는 고정적인 메모리로 할당받지만, 지역변수는 스택에 포함되어 함수가 불릴 때에 할당되고, 함수를 끝낼 때 사라진다
int global;/*전역변수. 모든 함수에서 인식된다*/
int test1(void)
{
int local; /*함수 내부에서 선언하였으므로 지역변수, 지역변수는 이 함수
내부에서만 인식된다.*/
local = 1;
global=2; /*전역변수이므로, 선언하지 않은 함수 내부에서도 인식된다.*/
}
int test2(void)
{
int local; /* 지역변수. 이 지역변수는 함수 test1()의 지역변수인 local과는
아무런 관계가 없다*/
int global; /*함수 내부에서 선언하였으므로 지역변수, 전역변수인 global과
이름이 같으므로 전역변수 global은 무시되고, 지역변수인 global
만이 인식된다.*/
local = 1;
global=2; /*전역변수인 global과는 아무런 관계가 없고 지역변수인 global로
동작한다.*/
}
static 변수
함수 내부에서 선언된 static 변수는 그 함수 안에서만 인식된다는 점에서는 것은 일반적인 지역변수와 동일하다. 그러나 함수 내의 일반 지역변수와는 달리 static 변수는 일정한 메모리를 할당받기 때문에 함수를 종료해도 사라지지 않고 계속 그 값을 유지한다.
void test(void)
{
int local=10; /* 지역변수. 함수에 진입할 때마다 10으로 초기화되고, 함수를 종료하면 사라진다 */
static int slocal=10; /* static형 변수, 함수를 처음 부를 때만 10으로 초기화되고 함수를 종료해도 그 값을 계속 유지한다*/
local++; /* 연산결과는 언제나 11이 된다*/
slocal++; /* 연산결과는 함수를 부를 때마다 증가한다 */
}
void main(void)
{
test(); /* 함수를 처음 부를 때 test()함수의 slocal의 값이 11로 초기화되고 slocal++ 연산결과 slocal의 값은 11가 된다*/
test(); /* 다시한번 test()함수를 부를 때 slocal의 값은 11로 유지하고 있으므로 slocal++ 연산 결과 slocal의 값은 12가 된다 */
}
주변기기를 제어하다 보면 바이트 단위보다는 비트단위가 편리할 때가 많다. 예를 들어 스텝모터(STEPPING MOTOR) 제어에서 비트신호를 어떻게 조합하느냐에 따라 회전방향이나 구동방법이 달라지게 된다. C언어에는 비트단위의 논리연산과 비트단위 제어를 위한 준비가 되어있다.
비트단위 논리 연산자
|
연산자 |
내용 |
예 |
|
& |
비트단위 논리곱(AND) |
y = a&0x0F; /*a의 하위 4비트만 얻는다.*/ |
|
| |
비트단위 논리합(OR) |
y = a|0x0F /*a의 하위 4비트를 모두 1로 만든다*/ |
|
~ |
비트단위 부정(1의 보수) |
y = ~0x00; /*y를 0xFF로 만든다*/ |
|
^ |
비트단위 배타적 OR |
y = a^0xFF; /*a중 해당비트가
1이면 0으로 만든다*/; |
|
<< |
우측으로 이동 |
y = 0x1<<2; /*0x1을 우측으로 2비트 이동, y=0x4가 된다*/ |
|
>> |
좌측으로 이동 |
y = 0x2>>1; /*0x2를 좌측으로 1비트 이동,y=0x1이 된다*/ |
비트필드(BIT FIELD)
비트필드는 정수형 변수를 비트 단위로 쪼개어 각 비트(또는 복수의 비트)에 해당하는 필드를 제어하도록 만든 변수형으로 다음과 같이 정의한다.
sturct 구조체명{
unsigned 필드1:비트폭;
unsigned 필드2:비트폭;
:
}변수명;
각 필드에 대한 접근은 구조체(struct)와 동일하게 ‘.’ 연산자를 이용한다.
struct BITS{
unsigned B0:1; /*LSB*/
unsigned B1:1;
unsigned B2:1;
unsigned B3:1;
unsigned B4:1;
unsigned B5:1;
unsigned B6:1;
unsigned B7:1; /*MSB*/
}b; /*총 8비트(1바이트)의 비트필드를 b라는 변수에 할당*/
b.B0 = 1; /* b변수의 첫번째 비트(LSB)를 1로 만든다*/
b.B6 = 0; /* b변수의 7번째 비트를 0으로 만든다.*/
disable() 함수
80x86 CPU의 하드웨어 인터럽트를 금지시킨다. 이 함수는 어셈블러 CLI(clear interrupt)와 동일한 효과를 낸다. disable()함수는 80x86 CPU 코어에게 인터럽트 금지 명령을 내리는 것이고, 주변 하드웨어에 직접적인 역할을 수행하지는 않는다. IBM-PC에서 인터럽트와 관련된 하드웨어 부품은 80x86 CPU와 8259A PIC(프로그래머블 인터럽트 제어기)이다.
386EX 프로세서에는 80x86과 소프트웨어적으로 호환되는 386SX CPU코어 이외에도 하드웨어 인터럽트를 관장하는 인터럽트 제어기인 8259A가 내장되어 있다. TB-386EX 보드에서는 하드웨어 인터럽트 제어기가 IBM-PC/AT와 동일하게 2개로 구성되어 있으며, 하나는 마스터 8259A로 또 하나는 슬레이브 8259A로 설정되어 있다. 모든 하드웨어 인터럽트는 마스터 8259A의 출력핀을 통해 CPU 코어(386SX)에 연결되는데, enable()과 disable() 함수는 마스터 8259A의 출력핀과 CPU 코어의 연결 여부를 결정한다.
8259A 입력핀에 요구한 인터럽트 (IRQ0,IRQ1..)는 8259A의 내부 레지스터에 의해 인터럽트 우선순위, 금지/허가 여부가 처리된 후 인터럽트를 발생할 조건이 되면 8259A가 80x86 CPU에 인터럽트를 걸게 된다. 이 때 8259A로부터 요구된 인터럽트를 금지하는 것이 바로 disable()함수의 역할이다.
enable() 함수
80x86 CPU의 하드웨어 인터럽트를 허가한다. 이 함수는 disable()함수와 반대되는 동작을 하며, 다음과 같이 정의되어 있다.
#include <dos.h>
void enable(void);
void disable(void);
FP_SEG(), FP_OFF()
far 포인터 (1MB 어드레스 공간을 지시하는 포인터)의 세그먼트와 옵셋을 얻는 매크로 함수. far 포인터는 4바이트로 구성되어 있으며, 위쪽의 2바이트는 세그먼트를 나타내고 아래쪽의 2바이트는 옵셋을 나타낸다.
unsigned int seg,off;
void far* fp=0x12345678L; /* far 포인터 fp를 정의하고 fp에 0x12345678 즉,
fp = 0x1234:0x3456 로 만든다.*/
seg=FP_SEG(fp); /* fp의 세그먼트(=0x1234)를 얻는다 */
off=FP_OFF(fp); /* fp의 옵셋(=0x3456)을 얻는다*/
peek(), peekb(), poke(), pokeb()
주어진 세그먼트/옵셋이 지시하는 메모리에 데이타를 읽고/쓰는 함수이다.
unsigned char byte;
unsigned int word;
unsigned int seg=0x1234,off=0x3456;
byte = peekb(seg,off); /* 메모리의 seg:off에서 1 바이트를 읽는다 */
word = peek(seg,off); /* 메모리의 seg:off에서 2 바이트(=1워드)를 읽는다 */
pokeb(seg,off,byte); /* 메모리의 seg:off에서 1 바이트 데이타(byte)를 쓴다 */
poke(seg,off,word); /* 메모리의 seg:off에서 2 바이트 데이타(word)를 쓴다 */
inport(), inportb(), outport(), outportb()
V25, 386EX 프로세서는 80x86과 마찬가지로 메모리 맵과 IO맵이 구별되어 있다. peek(), poke() 함수가 메모리를 다루는 함수인 반면, inport(), outport()는 IO포트로부터 데이타를 주고받는 역할을 한다.
unsigned char byte;
unsigned int word;
unsigned int port=0x100;
byte = inportb(port); /*IO포트의 port번지(=0x100)에서 1 바이트를 읽는다*/
word = inport(port); /*IO포트의 port번지(=0x100)에서 2 바이트를 읽는다*/
outportb(port,byte); /*IO포트의 port번지(=0x100)에 1 바이트(=byte)를 쓴다*/
outport(port,word); /*IO포트의 port번지(=0x100)에 2 바이트(=word)를 쓴다*/
setvect(), getvect()
내장형제어기를 꾸미다 보면 자신만의 인터럽트 루틴을 다루어야 할 경우가 생긴다. 주기적으로 키보드를 검사해서 무슨 키가 눌려졌나를 알아낸다던가, 외부에서 인터럽트 요구가 왔을 때, 적절한 응답을 해주는 경우에 사용하는 함수다. 이 함수에서는 함수 포인터를 사용하는데, 함수 포인터에 친숙하지 않는 사람은 예제를 공식처럼 외워서 쓰고, 기회가 있을 때 자세한 원리를 파악해도 지장이 없을 것이다.
새롭게 설치할 인터럽트는 void interrupt 라는 터보-C의 키워드로 시작해야 한다. 이 예제는 타이머 인터럽트(인터럽트 벡터번호 0x1Ch)를 새로 만들어 타이머 인터럽트가 걸릴 때마다 timer_count라는 변수를 1씩 증가시키는 동작을 하는 것이다.
/* 새로운 타이머 인터럽트 루틴을 만든다 */
/* TB-386EX 보드의 타이머 인터럽트(int 1Ch)는 하드웨어 인터럽트이며 10ms마다 인터럽트가 발생한다 */
/* 새롭게 작성한 인터럽트 루틴 new_int1Ch */
int timer_count=0; /* 전역변수를 선언한다.*/
void interrupt new_int1Ch(void)
{
enable(); /* 또 다른 인터럽트를 허가한다 */
timer_count++; /* 인터럽트가 걸릴 때마다 timer_count값을 증가시킨다 */
}
void my_function(void)
void main(void)
{
void interrupt (*old_int1Ch)(); /* 인터럽트함수 포인터 */
old_int1Ch = getvect(0x1C); /* 인터럽트 0x1C 서비스루틴의 번지를 old_int1Ch에 저장한다*/
setvect(0x1C,new_int1Ch); /* 새로운 인터럽트(new_int1Ch)를 설치한다 */
my_function(); /* 일반 함수를 실행한다 */
setvect(0x1C,old_int1Ch); /* 모든 일이 종료되면 원래의 인터럽트(old_int1Ch)를 인터럽트번호 0x1Ch로 복구한다 */
}
int86()
80x86 CPU에 소프트웨어 인터럽트를 발생시키는 함수이다. 레지스터에 적당한 값을 넣고, 인터럽트를 걸어서, 인터럽트루틴에서 처리한 레지스터의 값을 얻을 수 있다. 이 함수에서는 터보-C에서 제공하는 REGS라는 공용체(union)와 이 공용체의 항목인 두개의 구조체 WORDREGS, BYTEREGS를 사용하여 레지스터 값을 전달한다.
struct WORDREGS {
unsigned int ax, bx, cx, dx, si, di, cflag, flags;
}; /* 8개의 워드(2바이트) 단위 레지스터를 항목으로한 구조체*/
struct BYTEREGS {
unsigned char al, ah, bl, bh, cl, ch, dl, dh;
}; /* 8개의 바이트단위 레지스터를 항목으로 한 구조체*/
unionREGS{
structWORDREGS x; /*레지스터를 워드 단위로 조작*/
structBYTEREGS h; /*레지스터를 바이트 단위로 조작*/
};
REGS 공용체의 항목은 8워드(16바이트) 크기의 WORDREGS와 4워드(8바이트) 크기의 BYTEREGS이고, WORDREGS와 BYTEREGS가 겹치는 아래쪽 4워드는 WORDREGS의 항목과 BYTEREGS의 항목으로 자유롭게 접근할 수 있다. 즉 REGS 형의 변수 r을 다음과 같이 선언하면 80x86의 CPU 레지스터 AX를 다음과 같이 조작할 수 있다. 참고로 80x86의 AX레지스터는 16비트 레지스터이며 상위 8비트를 AH, 하위 8비트를 AL로 표기한다. 80x86의 BX, CX, DX 레지스터도 같은 방법을 적용한다.
REGS r;
r.x.ax=0x1234 /* 의사적으로 80x86 CPU의 AX 레지스터에 0x1234를 할당한다.*/
/* AX 레지스터에 대한 워드단위 처리는 다음과 같이 바이트 단위로 해도
동일한 결과를 낸다*/
r.h.ah=0x12; /* 의사적으로 AH에 0x12를 할당 */
r.h.al=0x34; /* 의사적으로 AL에 0X34를 할당 */
int86()함수를 사용한 예를 본다.
REGS rin,rout;
rin.h.ah = 0x0E;
rin.h.al = 'S';
int86(0x10,&rin,&rout); /* 인터럽트 0x10h (비디오 인터럽트)를 건다. 이때 AH=0x0E, AL='S'의 값을 넣는다. 처리결과는 rout에 실려 나온다 */