728x90

전처리기가 #include 지시자를 발견하면, 그 뒤를 따르는 파일 이름을 찾아 그 파일의 내용을 현재 파일에 포함시킨다.

 

소스코드 파일에 있는 #include 지시자는, 포함되는 파일에 있는 텍스트로 대체된다. 이것은, 사용자가 컴퓨터 앞에 앉아 포함되는 파일의 전체 내용을 소스파일의 특정 위치에 타이핑하는 것과 효과가 같다.

 

#include 지시자의 두가지 형태

#include <stdio.h>     <- 파일 이름을 < >(꺾쇠 괄호)로 둘러 싼다.

#include "mystuff.h"    <- 파일 이름을 " "로 둘러 싼다.

 

꺾쇠 괄호(< >)는 전처리기에게 하나 또는 그 이상의 표준 시스템 디렉토리에서 그 파일을 찾으라고 지시한다.

큰 따옴표(" ")는 현재 작업 디렉토리에서 그 파일을 찾으라고 지시한다. 파일이 없으면, 표준 시스템 디렉토리에서 찾는다.

ex)

#include <stdio.h> <-시스템 디렉토리들에서 찾는다.

#include "hot.h"     <-현재 작업 디렉토리에서 찾는다.

#include "/usr/biff/p.h" <-/usr/biff 디렉토리에서 찾는다.

 

파일을 포함시키는 이유는 컴파일러가 요구하는 정보를 헤더파일이 가지고 있기 때문이다.

일반적으로 stdio.h 파일에는 EOF, NULL, getchar(), putchar()의 정의가 들어있다. getchar()와 putchar()는 매크로 함수로 정의되어 있다. 또한 C 입출력(I/O) 함수들의 프로토 타입이 들어있다.

 

확장자 .h 헤더파일(header file)에는 프로그램의 선두에 위치시켜야하는 정보를 가지고 있다. 헤더파일에는 대개 전처리기 문장들이 들어가 있으며, stdio.h와 같은 헤더파일들은 컴파일러에 내장되어 오지만, 사용자는 자신의 헤더파일을 자유롭게 만들 수 있다.

 

큰 헤더파일을 포함한다고 해서 프로그램의 크기가 커지는 것은 아니다. 헤더파일의 내용이 최종코드에 추가되는 것이 아니라 컴파일러가 최종코드를 만들기 위해 헤더파일 내에 있는 정보를 사용한다.

 

헤더파일의 사용

일반적으로 헤더파일의 내용은 다음과 같다.

 - 명단상수(Manifest contant) : 전형적인 stdio.h 파일은 EOF, NULL, BUFSIZE(표준 입출력 버퍼의 크기)를 정의한다.

 - 매크로 함수(Macro function) : 예를 들어, getchar()는 getc(stdin)으로 정의되어 있다. getc()는 다소 복잡한 매크로로 정의 되어 있다. ctype.h헤더는 ctype 계열 함수들의 매크로 정의들을 가지고 있다.

 - 함수 선언(Function declaration) : string.h는, 문자열 계열 함수들의 함수 선언이 들어있다. 이 선언은 함수 프로토 타입 형식이다.

 - 구조체 템플릿 정의(structure template definition) : 표준 입출력 함수들은 FILE을 가리키는 포인터를 전달인자로 사용한다. stdio.h는 일반적으로 FILE을, 구조체를 가리키는 포인터로 만들기 위해 #define 또는 typedef를 사용한다. 이와 비슷하게, size_t 형과 time_t형도 헤더파일들에 정의되어 있다.

 

 

728x90

'Programming > C' 카테고리의 다른 글

인라인 함수 와 _Noreturn 함수  (0) 2019.07.17
전처리기 지시자와 매크로  (0) 2019.07.17
C #define에 전달인자 사용하기  (0) 2019.07.10
C 전처리기와 C 라이브러리  (0) 2019.07.10
C 비트 연산 - 정리  (0) 2019.07.10
728x90

#define에 전달인자를 사용함으로써, 함수처럼 보이고 함수처럼 행동하는, 함수 같은 매크로(function-like macro)를 만들 수 있다. 함수 같은 매크로 정의는 하나 또는 그 이상의 전달인자를 괄호 안에 넣는다.

ex)

#define A(X,Y)  (((X)+(Y)))

위 정의는 프로그램에서 다음과 같이 사용된다.

int z = A(3,4);

 z == 7

 

ex) 

#define SQUARE(X) X*X

int main

{

       int x = 5;

       printf("%d", SQUARE(x+2));

}

 

위 출력은 49로 예상하지만 실제 출력은 17이다. 예상과 다른 결과가 나오는 이유는, 전처리기는 계산을 하지 않고, 단순히 문자열만 대체하기 때문이다. 위 매크로에서 X를 X+2로 대체한다. 그래서 X*X 는 X+2 * X+2가 된다. 그러므로 5+2*5+2 가 되어 출력은 17이 된다. 

 

위 예제는 함수호출과 매크로 호출의 차이를 보여준다. 함수 호출은 전달인자의 값을 프로그램이 실행중일 때 함수에 전달한다. 매크로 호출은 전달인자 토큰을 컴파일 이전에 프로그램에 전달한다. SQUARE(x+2)의 결과가 49가 되게 ㅎ려면 괄호를 사용해야 한다. #define SQUARE(X)  (X)*(X)

 

일반적으로, 증가 연산자와 감소연산자를 매크로와 함께 사용하지 않아야한다.

 

매크로 전달인자로 문자열 만들기 : #연산자.

#define PSQR(X) printf("x의 제곱은 %d이다.\n",((X)*(X)))

위와 같은 매크로가 있을 때 PSQR(8)의 출력은

x의 제곱은 64이다.

와 같다.

 

큰 따옴표 안에 있는 X는 대체되는 토큰이 아니라 일반적인 텍스트이다. 함수 같은 매크로의 몸체(대체 리스트)에 있는  #기호는, 토큰을 문자열로 변환하는 전처리기 연산자이다.

ex)

#define PSQR(X) printf(""#X"의 제곱은 %d이다.\n",((X)*(X));

라고 가정하고 int y = 5; 일때

PSQR(y)는

y의 제곱은 25이다.

라고 출력된다.

PSQR(2+4)

2+4의 제곱은 3이다

라고 출력된다.

 

가변전달인자 매크로 : ...과 __VA_ARGS__

매크로 정의 전달인자 리스트의 마지막 전달인자로 생략기호(마침표 3개)를 사용할 수 있다는 것이다. 생략기호를 사용한다면, 미리 정의된 매크로 __VA_ARGS__를 사용할 수 있다.

ex)

#define PR(...) printf(__VA_ARGS__)

PR("잘 있었나!");

PR("무게 = %d,배송료 = $%.2f\n",wt,sp);

결과적으로 다음과 같다.

printf("잘 있었나!");

printf("무게 = %d,배송료 = $%.2f\n",wt,sp);

 

생략기호는 반드시 마지막 매크로 전달인자에 대해서만 사용해야 한다.

 

매크로는, 조심해서 사용하지 않으면 엉뚱한 부작용을 일으킬 수 있어 함수를 사용하는 것보다 까다롭다.

매크로와 함수중 선택하는 것은, 시간 절약이냐 메모리 절약이냐 놓고 선택하는것과 같다. 매크로를 20번 사용하는 것은, 프로그램에 코드 20라인을 넣는것과 같다. 함수는 20번 사용하더라도 함수 문장들이 프로그램에 단 한번 사용한다. 매크로에 비해 함수가 메모리 공간을 적게 차지한다. 그러나 함수가 있는 곳으로 프로그램 제어가 매번 이동했다가 다시 호출 프로그램으로 돌아와야 함으로 시간은 더걸린다.

 

 

매크로는 변수의 데이터형에 신경쓸 필요가 없다는 것이 장점이다. 매크로는 int형 float형에 모두 사용할 수 있다.

 

일반적으로 프로그래머들은, 다음과 같은 간단한 함수들을 위해 매크로를 사용한다.

#define MAX(X,Y) ((X) > (Y) ? (X) : (Y))

#define ABS(X) ((X)< 0 ? -(X) : (X))

#define ISSIGN(X) ((X)=='+' || (X) == '-' ? 1 : 0)

 

매크로 사용 팁

매크로 이름에는 스페이스가 없지만 대체 문자열에는 스페이스가 나타날 수 있다.

개별 전달인자의 앞뒤에 그리고 정의 전체를 괄호로 둘러 싸라.

매크로 함수의 이름에는 대문자를 사용하라.

728x90

'Programming > C' 카테고리의 다른 글

전처리기 지시자와 매크로  (0) 2019.07.17
파일 포함 시키기 : #include  (0) 2019.07.17
C 전처리기와 C 라이브러리  (0) 2019.07.10
C 비트 연산 - 정리  (0) 2019.07.10
C 비트 필드  (0) 2019.07.10
728x90

전처리기 : 프로그램이 컴파일 되기 전 그 프로그램을 처리한다. 전처리기는, 프로그램에 들어 있는 기초 약어들을 전처리기 지시자 뒤에 지시한 것으로 대체한다. 전처리기는 요청하는 다른 파일들을 프로그램에 포함시킬 수 있다. 또한 컴파일러에게 선택적으로 코드를 보여줄 수 있다.

 

컴파일러의 프로그램 번역 단계

1. 소스코드에 나타나는 문자들을 소스 문자 집합으로 매핑한다.

2. 뒤에 개행 문자가 있는 백슬래쉬를 모두 찾아서 없앤다.

ex) printf("That's wond\

                erful!\n");                 -> printf("That's wonderful!\n");

3. 텍스트를 전처리 토큰들의 시퀀스와 화이트 스페이스와 주석의 시퀀스로 분리한다.(토큰은 스페이스에 의해 서로 분리된 텍스트 그룹이다.) 이때 주석은 하나의 스페이스 문자로 대체된다.

ex) int/*       스페이스 문자 하나 */ fox;

-> int fox;

4. 컴파일러에 따라 화이트 스페이스 문자들의 각 시퀀스를 하나로 대체한다.

 

위 과정들 이후 전처리 단계에 들어간다.

 

#define 전처리기 지시자와 다른 전전처리기 지시자들은 라인의 시작 위치에서 #기호로 시작한다. 지시자는 소스파일 어디에나 둘 수 있으며 지시자가 나타난 곳에서부터 파일의 끝까지 효력을 미친다.

 

전처리기 지시자는 #에서 부터 처음 만나는 개행 문자 까지이다. 즉 한 라인으로 한정된다. 백슬래쉬-개행 문자 조합은 전처리가 시작되기 전에 제거된다. 그러므로 하나의 지시자를 몇개의 물리적인 라인에 걸쳐 놓을 수 있다.

 

#define 라인은 세부분으로 이루어진다.

#define TWO 2

에서 첫번째 부분인 #define 은 지시자 자체이다.

두번째 부분은 매크로라고 부르는 사용자가 선택한 약어이다. 위에서는 TWO

위 예제와 같이 값을  나타내는 매크로는 객체 같은 매크로(object-like macro)라고 부른다.

매크로 이름은 글자, 숫자, 밑줄 만 사용할 수 있고 숫자를 첫 문자로 사용할 수 없다. 세번째 부분이 대체리스트(replacement list) 또는 몸체(body)이다.

전처리기는, 프로그램에서 매크로를 만나면 그것을 몸체로 대체한다.

 

어떠한 문자열이라도 심지어 C의 표현식 ( ex) printf(~)) 까지도 매크로로 표현할 수 있다.

 

토큰

매크로의 몸체는 토큰으로 구성된 문자열로 간주된다.

C 전처리기의 토큰은 매크로 정의의 몸체에 있는 문리된 단어들이다.

728x90

'Programming > C' 카테고리의 다른 글

파일 포함 시키기 : #include  (0) 2019.07.17
C #define에 전달인자 사용하기  (0) 2019.07.10
C 비트 연산 - 정리  (0) 2019.07.10
C 비트 필드  (0) 2019.07.10
C 비트 연산  (0) 2019.07.10
728x90

정리

C는 두가지 접근 방법으로 비트들에 접근한다.

1. 비트 단위 연산자들을 사용

2. 구조체 비트 필드 생성

 

컴퓨팅 하드웨어는 2진수 체계와 일접한 관련이 있다.

메모리와 레지스터에 있는 비트들의 on/off 상태를 2진수인 1과 0으로 나타낼 수 있기 때문이다.

 

2진수와 관련된 8진수 표기와 16진수 표기를 인식한다. 2진수 숫자 하나가 1비트를 나타내듯이, 8진수 숫자 하나는 3비트를 나타내고, 16진수 숫자 하나는 4비트를 나타낸다. 이러한 관계로 인해 2진수를 8진수나 16진수로 쉽게 변환할 수 있다.

 

C는 몇가지 비트 단위 연산자를 제공한다. 비트단위 연산자는 어떤 값에 있는 개별적인 비트들을 독립적으로 처리할 수 있기 때문이다.

1. 비트 단위 부정 연산자(negation : ~) : 피연산자에 있는 각 비트들을 반전시킨다. 즉 1->0, 0->1

2. 비트 단위 논리곱 연산자(AND : &) : 두 피연산자로 부터 하나의 값을 만든다. 서로 대응하는 위치에 있는 각 비트들이 둘다 1이면 결과 비트가 1이고 둘중에 하나 이상이 0이라면 해당 결과 비트도 0이다.

3. 비트 단위 논리합 연산자 (OR : |) : 두 피연산자로 부터 하나의 값을 만든다. 이때 두 피연산자의 서로 대응하는 비트중 하나라도 1이면 결과 비트가 0이 된다.

4. 비트 단위 배타적 논리합 연산자(Exclusive OR : ^) : 두 피연산자로 부터 하나의 값을 만든다. 대응하는 비트가 다를때 1이고 대응하는 비트가 0이면 결과 비트도 0이다.

 

C는 왼쪽 시프트 연산자(<<)와 오른쪽 시프트 연산자(>>)를 제공한다.

이 두 연산자는 주어진 비트 수만큼 비트 패턴을 왼쪽이나 오른쪽으로 이동시켜 새로운 값을 만든다. 왼쪽 시프트 연산의 경우 이동시키면서 비워지는 비트에 0을 채운다. 오른쪽 시프트 연산의 경우 값이 unsigned 형이라면 비워지는 비트에 0을 채우지만, signed 데이터형의 경우 컴파일러에 따라 다르다.

 

728x90

'Programming > C' 카테고리의 다른 글

C #define에 전달인자 사용하기  (0) 2019.07.10
C 전처리기와 C 라이브러리  (0) 2019.07.10
C 비트 필드  (0) 2019.07.10
C 비트 연산  (0) 2019.07.10
C언어 공부 37  (0) 2019.07.08
728x90

비트를 조작하는 두번째 방법은, 비트필드(bit field)를 사용하는 것이다. 비트 필드는 하나의 signed int 형 또는 하나의 unsigned int형 안에 있는 이웃하는 비트들의 집합이다.

 

비트 필드는 각 필드에 레이블을 붙이고, 그들의 크기를 지정하는 구조체 선언을 통해 설정한다. 비트 필드들로 이루어진 구조체는 하나의 단위 안에 여러개의 설정을 저장할 수 있다.

ex)

struct Flags {

unsigned int a : 1;          //1비트 크기

unsigned int b : 4;            //4비트 크기

unsigned int c : 7;          //7비트 크기

}bits;

 

위 코드는 1비트짜리 필드 1개와 4비트짜리 필드 1개, 7비트짜리 필드 1개를 만든다.

각 필드에 값을 대입할 수 있다.

bits.a = 0;

bits.b = 3;

bits.c = 102;

대입하는 값이 필드의 수용능력을 벗어나지 않도록 주의하라.

 

선언한 비트들의 총 개수가 unsigned int의 크기를 초과하면 그 다음 위치의 unsigned int 기억 장소가 사용된다.

 

하나의 필드가 두 unsigned int의 경계에 양다리를 걸칠 수 없다. 경계에 양다리를 걸치는 필드 정의는 컴파일러가 unsigned int 경계에 맞게 자동으로 정렬한다.

 

테이블이 없는 필드 크기를 지정하여, 레이블이 없는 구멍이 있는 필드 구조체를 메울 수 있다. 레이블이 없는 필드 크기를 0으로 지정하면, 그 다음에 오는 필드가 그 다음 정수에 맞게 정렬된다.

ex)

struct {

unsigned int field1 : 1;

unsigned int         : 2;

unsigned int field2 : 1;

unsigned int         : 0;

unsigned int field3 : 3;

}stuff;

 

여기서 stuff.field1과 stuff.field2 사이에는 2비트짜리 갭이있다. 그리고 stufffield3은 그 다음 위치의 int에 저장된다.

 

 

 

비트 필드 예제

흔히 비트필드는 데이터를 간결하게 저장하는 하나의 방법으로 사용된다. 예를들어, 화면 위에 표시되는 사각형의 속성을 나타내기로 결정했다고 가정하자. 아래는 사각형의 속성이다.

- 사각형은 불투명하거나 투명하다

- 채우기 색상은 black, red, green, yellow, blue, magenta, cyan, white 로 구성된 색상 팔레트에서 선택한다.

- 테두리는 보이게 하거나 숨길 수 있다.

- 테두리 색상은 채우기 색상과 마찬가지로 동일한 팔레트에서 선택한다.

- 테두리 선의 스타일은 실선(solid), 점선(dotted), 파선(dashed) 중에서 하나를 선택한다.

 

사각형이 불투명인지 투명인지를 나타내는 데에는 한 비트만 있으면 된다. 또한 테두리를 보이게 할것인지 숨길 것인지를 나타내는 데에도 한 비트만 있으면 된다. 8 가지의 색상 값들을 비트 3개짜리 한단위로 나타낼 수 있다. 3가지의 테두리선 스타일은 비트 2개짜리 한단위로 나타낼 수 있다. 그러므로 총 10비트만 있으면 된다.

 

위와 같은 정보를 표현하는 한가지 방법은, 채우기 관련 정보를 한 바이트에 넣고, 테두리 관련 정보를 다른 한 바이트에 넣기 위해 패딩(padding)을 사용하는 것이다.

ex)

struct box{

        bool opaque              :1;

        unsigned int fill-color   :3;

        unsigned int               :4;

        bool show_border       :1;

        unsigned int border_color  :3;

       unsigned int border_style   :2;

       unsigned int                   :2;

};

 

패딩이 위 구조체를 16비트 크기로 만든다. 패딩이 없으면 이 구조체는 10비트 크기가 될 것이다. 일반적으로 C는, 비트 필드로 이루어진 구조체의 기본 구성 단위로 unsigned int형을 사용한다. 그러므로 1비트짜리 필드 하나가 구조체를 구성하는 유일한 멤버인 경우에도, 그 구조체는 unsigned int형과 같은 크기(32bit)를 가진다.

 

opaque 멤버에 대해, 사각형이 불투명한 경우에 값 1, 투명한 경우에 값 0을 사용할 수 있다. show_border 멤버에도 테두리를 보일 경우에 값 1, 숨길경우에 값 0을 사용할 수 있다. 색상의 경우에는 간단한 RGB(red-green-blue)방식을 사용할 수 있다.

 

RGB는 빛 혼합의 3원색이다. 모니터는 red, green, blue 픽셀들을 혼합하여 다양한 색상을 구현한다. 컴퓨터 컬러의 초창기에, 각 픽셀은 on 또는 off가 될 수 있었다. 그래서 세2진수로 색상의 강도를 나타내는데 각각 1비트씩 사용할 수 있다.

일반적인 순서는 왼쪽 비트로 blue 강도, 가운데 비트로 green강도, 오른쪽 비트로 red 강도를 나타낸다. 이것들을 통해 8가지 조합을 나타낼 수 있다. 이들을 fill_color 와 border_color 멤버의 값으로 사용할 수 있다. 마지막으로 실선, 점선, 파선 스타일을 값 0,1,2 로 나타낼 수 있다.

 

간단한 색상 표현

     비트표현                                 10진수 표기                            색상

       000                                          0                                      Black

       001                                          1                                      Red

       010                                          2                                      Green

       011                                          3                                      Yellow

       100                                          4                                       Blue

       101                                          5                                       Magenta

       110                                          6                                       Cyan

       111                                          7                                       White

 

 

 

 

 

 

 

 

 

 

 

728x90

'Programming > C' 카테고리의 다른 글

C 전처리기와 C 라이브러리  (0) 2019.07.10
C 비트 연산 - 정리  (0) 2019.07.10
C 비트 연산  (0) 2019.07.10
C언어 공부 37  (0) 2019.07.08
C언어 공부 36  (0) 2019.07.08
728x90

비트단위 시프트 연산자

비트단위 시프트 연산자는 비트들을 왼쪽으로 또는 오른쪽으로 이동시킨다.

 

왼쪽으로 시프트 : <<

왼쪽으로 시프트 연산자(<<)는, 왼쪽 피연산자의 비트들을 오른쪽 피연산자가 지정하는 자릿수만큼 왼쪽으로 이동시킨다. 비워지는 위치들은 0으로 채워진다. 왼쪽 끝을 지나 밖으로 밀려나는 비트들은 사라진다.

(10001010) << 2

(00101000)

이 연산은 새로운 비트값을 만들지만 피연산자를 변경시키지 않는다.

변수값을 실제로 변경시키려면 왼쪽으로 시프트-대입 결합 연산자(<<=)를 사용할 수 있다. 이 연산자는  왼쪽 피연산자로 주어진 변수의 비트들을 오른쪽 피연산자가 지정하는 자릿수만큼 왼쪽으로 이동시킨다.

ex)

int stonk = 1;

int onkoo;

onkoo = stonk << 2;                   //onkoo에 4를 대입한다.

 

stonk <<= 2;                            //stonk를 4로 변경한다.

 

오른쪽으로 시프트 : >>

오른쪽으로 시프트 연산자(>>)는, 왼쪽 피연산자의 비트들을 오른쪽 피연산자가 지정하는 자릿수 만큼 오른쪽으로 이동시킨다. 오른쪽 끝을 지나 밖으로 밀려나는 비트들은 사라진다. 부호없는(unsigned) 데이터 형인 경우에, 왼쪽에 비워지는 위치들은 0 으로 채워진다. 결과값은 시스템에 따라 다르다. 왼쪽에 비워지는 위치들이 0으로 채워질 수도 있고, 부호 비트(제일 왼쪽 비트)가 복사되어 채워질 수 있다.

 

ex)

(10001010) >> 2                                //부호 있는값

(00100010)                                        //결과값 (일부 시스템에서)

(10001010)>>2                                 //부호 있는값

(11100010)                                       //결과 값(다른 시스템에서)

 

부호 없는 값의 경우에는 다음과 같다.

(10001010)>>2                                 //부호 없는값

(00100010)                                      //결과값(모든 시스템에서)

 

오른쪽 시프트-대입 결합 연산자 (>>=)는 왼쪽 피연산자로 주어진 변수의 비트들을 오른쪽 피연산자가 지정하는 자릿수만큼 오른쪽으로 이동시킨다.

int sweet = 16;

int ooosw;

 

ooosw = sweet >> 3;                             //ooosw = 2, sweet은 16

sweet >>= 3;                                       //sweet이 2

 

용도 : 비트단위 시프트 연산

비트 단위 시프트 연산자들은, 어떤 수를 2의 거듭제곱으로 곱하거나 나눌 때 편리하게 사용할 수 있습니다.

number << n  : number에 2의 n승씩 곱한다.

number >> n : number가 음수가 아니라면, number를 2의 n승씩 나눈다.

 

시프트 연산은, 10진수 체계에서 10을 곱하거나 10으로 나눌 때 소수점을 오른쪽 또는 왼쪽으로 이동시키는 것과 비슷하다.

 

CHAR_BIT 은 limit.h에 있는 매크로 이다. 이 매크로는 char에 있는 비트 수를 나타낸다. char 은 1byte 즉 8bit

CHAR_BIT은 8이다.

 

변수 n과 1개의 비트를 비교하려면 (0x1 & n)을 사용한다.

 

 

 

 

728x90

'Programming > C' 카테고리의 다른 글

C 비트 연산 - 정리  (0) 2019.07.10
C 비트 필드  (0) 2019.07.10
C언어 공부 37  (0) 2019.07.08
C언어 공부 36  (0) 2019.07.08
C언어 공부 35  (0) 2019.06.22
728x90

C의 비트 단위 연산자

C는 비트단위 논리연산자와 시프트 연산자를 제공한다.

 

비트단위 논리 연산자.

네개의 비트 단위 논리 연산자(char형을 포함한) 정수형 데이터에 적용된다. 이들을 비트 단위 연산자라고 부르는 이유는, 각 비트에 연산이 적용될 때 왼쪽이나 오른쪽에 있는 비트들에 영향을 받지 않기 때문이다.

 

1의 보수, 비트단위 부정(negation) 연산자 : ~

단항 연산자 ~는, 다음과 같이, 각 비트에 대해 1은 0으로, 0은 1로 바꾼다.

~(10011010) -> (01100101)

 

unsigned char 형인 val에 값 2가 대입되어 있다고 가정하자. 2진수 표기로 2는 00000010이다. 따라서 ~val은 값 11111101, 즉 253이다.

3 * val 이 val의 값을 변경시키지 않듯이, 이 연산자는 val의 값을 변경시키지 않는다. 그러므로 val은 여전히 2이다. val의 값을 ~val로 바꾸고 싶다면 대입을 사용하라.

val = ~val;

 

비트 단위 논리곱(AND)연산자 : &

이항 연산자 &는 두 피연산자를 대응하는 비트끼리 서로 비교함으로써 새로운 값을 만든다. 대응하는 두 비트가 모두 1일 때에만 결과 비트가 1이다.

(10010011) & (00111101) 의 값은 (00010001)이다.

 

C는 비트단위 논리곱 - 대입 결합 연산자 &= 를 제공한다.

val &=0377;

다음과 같은 문장과 동일하다.

val = val & 0377;

 

 

비트 단위 논리합(OR) 연산자 : |

이항 연산자  |는 두 피연산자를 대응하는 비트끼리 서로 비교함으로써 새로운 값을 만든다. 각각의 비트 위치에 대해, 피연산자들의 대응하는 두 비트중 어느하나라도 1이면 결과 비트가 1이다.

(10010011)|(00111101)의 결과는 (10111111)이다.

 

C는 비트 단위 논리합(OR) - 대입 연산자 |=를 제공한다. 

val |= 0377;

위 문장은 다음과 같다.

val = val | 0377;

 

비트단위 배타적 논리합(Exclusive OR)연산자 : ^

이항 연산자 ^는 두 피연산자를 대응하는 비트끼리 서로 비교함으로 써 새로운 값을 만든다. 피연산자들의 대응하는 두 비트중 어느 하나가(둘다가 아니라) 1이면, 결과 비트가 1이다.

(10010011) ^ (00111101)의 결과는 (10101110)이다.

 

C는 비트단위 배타적 논리합(Exclusive OR)- 대입 결합 연산자 ^=를 제공한다.

val ^= 0377;

위 문장은 다음과 같다.

val = val ^ 0377;

 

용도 : 마스크

비트 단위 논리곱 연산자는 흔히 마스크(mask)와 함께 사용된다. 

일부 비트들은 1, 일부 비트들은 0으로 설정된 비트 패턴을 마스크라 한다.

 

예를 들어 #define을 사용하여 기호 상수 MASK를 2로 정의 했다고 가정하자. 그럼 2는 2진수로 00000010이다.

그러면 다음과 같은 문장은

flags = flags & MASK;

flags의 하나의 비트를 제외한 모든 비트들을 0으로 설정한다. 그 이유는 & 연산자를 사용하여 0과 결합되는 비트는 결과 비트가 0이 되기 때문이다. 그러나 MASK의 1이 있는 위치의 flags 비트는 변하지 않는다.

 

위 논리를 확장하면, 마스크에 있는 0인 비트들을 불투명막으로, 1인 비트들은 투명막으로 생각할 수 있다. 표현식 flags & MASK는 flags의 비트패턴을 마스크로 가리는 것과 같다. 즉, MASK에 있는 1인 비트들에 대응하는 비트들만 볼 수 있다.

 

C에서 마스크의 가장 일반적인 용도는 다음과 같은 문장이다. ch & 0xff;

값 0xff는 2진수로 11111111이기 때문에, 이 마스크는 ch의 마지막 8비트만 남기고 모두 0으로 설정한다. 따라서 ch가 8비트, 16비트 또는 그 이상이라도 상관없이, 최종값을 1바이트에 넣을 수 있는 값으로 만들어 준다.

 

 용도 : 비트 켜기(On)(비트 설정)

어떤 경우 특정 비트들만 켜고, 나머지 비트들을 변경시키지 말아야할 경우가 있다. 이러한 경우에 비트 단위 논리합 | 연산자를 사용하면 된다.

 

예를 들어, 비트 1번이 1로 설정된 (00000010) MASK를 가지고 있다고 가정하자.

flags = flags | MASK;

flags의 비트 1번이 1로 설정되고, 나머지는 변경되지 않는다.

 

ex) flags는 00001111이고, MASK는 10110110이다.

flags |= MASK;

(00001111) | (10110110)

위 값은

(10111111)이다.

MASK에서 1로 정해진 모든 비트들은 결과적으로 1로 정해진다. MASK에서 0비트에 상응하는 flags에 있는 모든 비트들은 변경되지 않는다.

 

용도 : 비트 끄기(OFF)(비트 삭제)

어떤 경우 특정 비트들만 끄고, 나머지 비트들을 변경시키지 않는 것이 유용한 경우도 있다.

flags의 비트 1번만 끄기를 원한다고 가정하자. MASK는 비트 1번만 1로 설정되어 있다. 그러면 다음과 같은 문장을 사용할 수 있다.

flags = flags &~MASK;

~MASK는 비트 1번을 제외한 나머지 비트들이 모두 1이다. 11111101 이다. 또한 &를 사용하여 ~MASK의 1인 비트에 결합하는 비트는 변경되지 않고 0인 비트와 결합하는 비트는 0이 된다. 그러므로 원래의 값과 무관하게 0으로 설정된다.

 

flags가 00001111이고 MASK가 10110110이라고 가정하면 flags &= ~MASK;는

(00001111) &~(10110110)-> (00001111) &(01001001) -> (00001001)이 된다.

결과적으로 MASK에서 1로 설정된 모든 비트들은 0으로 설정된다. MASK에 0비트에 상응하는 flag에서의 모든 비트들은 변경되지 않고 남는다. flags&= ~MASK;

 

용도 : 비트 토글

비트를 토글(toggle)한다는 것은, on을 off로, off를 on으로 전환하는 것을 의미한다. 비트를 토글하기 위해서는 배타적 논리합(Exclusive OR) 연산자 ^를 사용할 수 있다. b가 비트세팅(1 또는 0)일때, b가 1이면 1^b는 0이다. b가 0이면 1^b 는 1이다. 또한 0 ^ b의 값이 무엇이든 간에 그대로 b가 된다. 그러므로 ^연산자를 사용하여 어떤 값을 마스크와 결합하면, 마스크에 있는 1인 비트에 대응하는 값들은 토글되고, 0인 비트에 대응하는 값들은 변하지 않는다.

 

예를 들면 flags가 00001111이고 MASK가 10110110이라고 가정하면 flags ^ MASK는 (00001111) ^ (10110110)이 된다. 이값은 (10111001)이 된다.

 

MASK에서 1로 설정된 모든 비트들이 토글되고 있는 flags의 비트에 결과적으로 상응하게 된다. MASK에 있는 0비트에 상응하는 flags 비트는 변경되지 않는다.

 

용도 : 비트 값 검사

특정 비트 하나의 값만 검사하고 싶다. flag에 있는 비트 1번이 1인지 검사하는 방법은 다음과 같이 할 수 있다.

if((flag & MASK) == MASK)

             puts("wow!");

위와 같은 방법을 사용한다면 이것은 특정 위치에 있는 비트를 검사하는 것이 아니라 모든 비트들의 세팅을 비교하는 것이다. 그로므로 비트 1번만 비교하려면 MASK를 사용하여 flag의 비트 1번을 제외한 모든 비트들을 마스크 처리 해야한다.

MASK = (00000010)

if((flag & MASK) == MASK)와 같은 과정을 통해 특정 비트의 값을 알 수 있다.

비트 연산자는 == 보다 우선순위가 낮다. 그러므로 flag&MASK를 괄호로 둘러싸야 한다.

마스크(MASK)의 비트 수가 마스크하려는 값(flag)의 비트수와 같거나 커야한다.

 

 

728x90

'Programming > C' 카테고리의 다른 글

C 비트 필드  (0) 2019.07.10
C 비트 연산  (0) 2019.07.10
C언어 공부 36  (0) 2019.07.08
C언어 공부 35  (0) 2019.06.22
C언어 공부 34  (0) 2019.06.22
728x90

비트조작

 

C에서는 시스템이 사용하는 문자 집합을 저장하는데 필요한 크기를 바이트(byte)라고 부른다. 그러므로 C에서는 1바이트가 8비트, 9비트, 16비트 또는 그밖의 어떤 비트 수를 가질 수 있다. 그러나 메모리 칩과 데이터 전송률을 표시하는 데에는 8비트 바이트가 사용된다. 보통 1바이트는 8비트이다.

 

   

7번 비트를 최상위 비트라고 하고 0번 비트를 최하위 비트라고 한다. 각각의 비트 번호는 하나씩 증가되는 2의 거듭제곱에 대응한다. 위의 예는 64 + 8 + 1, 73이다.

1바이트가 가질 수 있는 가장 큰 2진수는 11111111 이다. 이 값은 128 + 64 + 32 + 16 + 8 + 4 + 2 + 1 = 255 이다.

1바이트는 0부터 255까지 256개의 수를 저장할 수 있다. 또는 비트 패턴을 다르게 해석함으로써, 프로그램은 1바이트에 -128 ~ + 127 까지, 전체 256개의 수를 저장 할 수 있다.

 

unsigned char 형은 0 ~ 255 까지의 범위를 나타낸다. signed char 형은 -128 부터 + 127 까지 범위를 나타낸다.

 

부호 있는 정수

부호 있는 수들의 표현은 C가 아니라 하드웨어에 의해 결정된다. 부호 있는 수듥을 표현하는 가장 간단한 방법은 1비트를, 최상위 비트를 부호를 나타내는데 사용하는 것이다. 이것은 수 자체를 위해 7비트를 사용한다는 것을 의미한다. 따라서 전체 범위는 -127 부터 +127 까지이다.

 

위와 같은 접근에는 하나의 문제점이 있다. +0과 -0 두개의 0이 생기는 것이다. 하나의 값을 나타내는데 비트 패턴을 두개를 써버린다. 이 문제점을 해결하는 방법이 "2의 보수" 이다.

 

2의 보수를 1바이트값에 적용하여 설명하겠다. 이 방법은 0부터 127까지의 값들을, 0으로 설정된 최상위 비트를 뺀 나머지 7비트로 표현한다. 또한, 최상위 비트가 1이면 그값은  음수이다. 음수의 값을 결정하는 방법은 9비트 패턴 100000000(256)에서 그 음수의 패턴을 뺀 결과가 음수 값의 크기이다. 9비트 패턴 100000000(256의 2진수 표현)으로 부터 음수용 비트 패턴을 뺀 결과는 값의 크기이다. 

예를 들어, 패턴이 10000000이라고 하면 부호 없는 값은 128이다. 부호 있는 값은 100000000 - 10000000, 즉 10000000(128)이 된다. 그러므로 수는 -128이다.

마찬가지로, 10000001 은 -127 이고, 11111111 은 -1 이다. 이 방법은 -128 부터 + 127 까지의 범위에 있는 수들을 나타낸다.

 

2의 보수 방법으로 표현된 2진수의 부호를 뒤집는 가장 간단한 방법은, 각 비트를 반전시키고(0은 1로, 1은 0으로) 거기에 1을 더하는 것이다. 1은 00000001이기 때문에, 2의 보수로 -1은 11111110 + 1 즉 11111111이 된다.

 

2진 부동 소수점 수

2진 소수에서는 증가되는 2의 거듭제곱을 분모로 사용한다. 2진 소수 .101은 1/2 + 0/4 + 1/8 로 나타낸다. 이값의 10진 표기는 0.50 + 0.00 + 0.125 즉 0.625이다.

 

컴퓨터는 하나의 부동 소수점 수를 표현하기 위해, (시스템 마다 다른) 특정 개수의 비트들을 2진 소수를 저장하는데 사용한다. 그리고 추가적인 비트들에 지수를 저장한다. 부동 소수점 수의 실제 값은 2진 소수와 2진 지수의 곱으로 이루어진다.

 

8진수, 16진수

 

8진수 체계는 8을 기수로 사용하며, 숫자 0에서 7까지 사용한다. 8진수 451은 4 x (8 x 8) + 5 x 8+ 1 x 1 = 297(10진수) 이다.

 

8진수는 2진수의 세 숫자에 대응한다.

                      8진수                                                    2진수

                        0                                                        000

                        1                                                        001

                        2                                                        010

                        3                                                        011

                        4                                                        100

                        5                                                        101

                        6                                                        110

                        7                                                        111

 

 

 

 16진수 체계는 16을 기수로 사용한다.

숫자 0에서 15까지를 사용하며 10 ~ 15는 A~F로 나타낸다.

16진수 A3F는 10 x (16 x 16) + 3 x (16) + 15 x 1 = 2623(10진수)이다.

2623 을 0xa3f로 나타낸다.

 

16진수는 2진수의 4개의 숫자에 대응한다. 그러므로 16진수 숫자 2개는 정확히 8비트 즉 1바이트에 대응한다. 첫번째 숫자는 상위 4비트를 나타내고, 두번째 숫자는 하위 4비트를 나타낸다. 이것은 바이트 값들을 16진수로 나타내는 것을 자연스럽게 만든다.

 

C는 비트들을 다루는 두가지 기능을 제공한다. 첫번째 기능은, 비트들에 적용하는 여섯개의 비트단위 연산자들로 이루어진 집합이다. 두번째 기능은, 필드(field) 데이터 형식이다. 이것을 사용하여 하나의 int형 안에 있는 비트들에 접근할 수 있다.

 

 

728x90

'Programming > C' 카테고리의 다른 글

C 비트 연산  (0) 2019.07.10
C언어 공부 37  (0) 2019.07.08
C언어 공부 35  (0) 2019.06.22
C언어 공부 34  (0) 2019.06.22
C언어 공부 33  (0) 2019.06.22

+ Recent posts