공부했던 자료 정리하는 용도입니다.

재배포, 수정하지 마세요.

 

 


 비트 연산자 

  • 비트 연산자의 종류
  • 시프트 연산
  • 플래그(flag)와 마스크
  • XOR 연산의 특성

 


 

비트연산자

  비트 연산자는 비트로 옵션을 설정할 때 주로 사용하며 저장 공간을 아낄 수 있는 장점이 있다. 이와 같은 방식을 플래그(flag)라고 하며, 연산과 할당이 한꺼번에 이루어지는 연산자는 플래그를 켜거나 끌 때 유용하게 활용된다.

 

  • bit : 2진수를 저장하는 단위. 컴퓨터에서 사용하는 최소 단위이며 0과 1을 나타낸다.
    최상위 비트(Most Significant Bit, MSB) : 첫 번째 비트 
    최하위 비트(Least Significant Bit, LSB) : 마지막 비트
  • byte : 8 bit 크기의 단위

 

  ■ 비트 연산자의 종류

연산자 설  명
& 비트 AND
| 비트 OR
^ 비트 XOR(배타적 OR, Exclusive OR)
~ 비트 NOT
<< 비트를 왼쪽으로 시프트
>> 비트를 오른쪽으로 시프트
&= 비트 AND 연산 후 할당
|= 비트 OR 연산 후 할당
^= 비트 XOR 연산 후 할당
<<= 비트를 왼쪽으로 시프트한 후 할당
>>= 비트를 오른쪽으로 시프트한 후 할당

 

 

시프트 연산

[변수] << [이동할 비트 수];
[변수] >> [이동할 비트 수];

  지정한 횟수대로 비트를 이동시킨다. ``<<``은 2의 거듭제곱을 곱한 효과, ``>>``은 2의 거듭제곱을 나눈 결과와 같다. 시프트 연산은 자료형의 부호 여부에 따라 동작과정이 다르다. 부호있는 자료형에 시프트 연산을 하는 경우 의도치 않은 결과가 나올 수 있으므로 항상 부호 비트를 고려해서 연산해야 한다.

  • 부호 없는 자료형 : 첫째 자리나 마지막 자리를 넘어서는 비트는 사라지며, 모자라는 공간은 0으로 채운다.
  • 부호 있는 자료형 : 부호 있는 자료형과 방법은 같지만 ``>>``일 경우 부호비트로 채워지고, ``<<``일 경우 0으로 채워진다. 이 과정에서 부호 비트를 덮어씌우게 되면 부호가 바뀐다.

 

 

플래그(flag)와 마스크

  플래그는 적은 공간에 정보를 저장해야 하고, 빠른 속도가 필요할 때 사용한다.(ex CPU) 플래그와 마스크를 OR연산하면 플래그를 켤 수 있다(이미 켜진 경우에는 그대로 유지) 비트를 끌때는 NOT연산을 이용하고 특정 비트가 켜져 있는지 검사할 때는 &연산자를 사용한다.

 

  • 마스크(mask) : 플래그의 비트를 조작하거나 검사할 때 사용하는 숫자
  • 토글(toggle) : 두 가지 상태만을 가지고 있는 스위치이다. 누를 때마다 값이 전환된다.
#include <stdio.h>

int main(void)
{
	int i;
	int result;
	unsigned char flag = 0;
	printf("before : %u\n", flag);
	
	// 10진수 2진수로 변환해서 출력 
	result = flag;
	for(i = 128; i >= 1 ; i = i / 2)
	{
		printf("%d", result / i);
		if (i == 16)
			printf(" ");
		result %= i;		
	}
	
	flag |= 1;	// 마스크값 0000 0001과 OR연산해서 8번째 비트를 켠다.
	flag |= 2;	// 마스크값 0000 0010과 OR연산해서 7번째 비트를 켠다. 
	flag |= 4;	// 마스크값 0000 0100과 OR연산해서 6번째 비트를 켠다. 
	
	printf("\n------------------\n");
	printf("6 ~ 8번째 켠 후: %u\n", flag);	// 0000 0111이므로 7
	
	// 10진수 2진수로 변환해서 출력 
	result = flag;
	for(i = 128; i >= 1 ; i = i / 2)
	{
		printf("%d", result / i);
		if (i == 16)
			printf(" ");
		result %= i;		
	}
		
	flag &= ~2;	// 마스크값 2의 비트를 뒤집은뒤(1111 1101) AND연산해서 7번째 비트를 끈다. 	
	printf("\n------------------\n");
	printf("7번째 끈 후 : %u\n", flag);	// 0000 0101이므로 5
	
	// 10진수 2진수로 변환해서 출력 
	result = flag;
	for(i = 128; i >= 1 ; i = i / 2)
	{
		printf("%d", result / i);
		if (i == 16)
			printf(" ");
		result %= i;		
	}
	
	flag ^= 1;	// 마스크값 0000 0001과 XOR 연산해서 8번째 비트 토글 
	flag ^= 8;	// 마스크값 0000 1000과 XOR 연산해서 5번째 비트 토글 
	
	printf("\n------------------\n");
	printf("5, 8번째 토글 후 : %u\n", flag);	// 0000 1100이므로 12

	// 10진수 2진수로 변환해서 출력 
	result = flag;
	for(i = 128; i >= 1 ; i = i / 2)
	{
		printf("%d", result / i);
		if (i == 16)
			printf(" ");
		result %= i;		
	}
	return 0;
}

플래그를 사용하는 예시이다.

``플래그 |= 마스크`` 해서 플래그를 켜고, ``플래그 &= ~마스크``해서 플래그를 끈다. (토글은 ``플래그 ^= 마스크``)
``플래그 &= 마스크``해서 플래그가 켜져있는지 검사한다. 

 

 

XOR 연산의 특성과 사용예시

a ^ b = c
c ^ b = a
c ^ a = b

XOR은 위와 같은 특성을 가진다.

  • 간단한 암호화
    평문과 암호키를 비트 XOR연산하면 암호화된 값이 나온다. 암호화된 값과 암호키를 다시 XOR연산하면 값이 복호화되어 평문이된다.
  • 임시 변수 없이 두 변수의 값을 바꾸기
    임시 변수없이 XOR 연산자를 세 번 사용해서 두 변수의 값을 바꿀 수있다.
#include <stdio.h>

int main()
{
    int a = 10;
    int b = 20;
    
    printf("a : %d, b : %d\n", a, b);
    
    a = a ^ b;
    b = a ^ b;
    a = a ^ b;
    
    printf("a : %d, b : %d\n", a, b);
    return 0;
}

XOR 연산자로 두 변수의 값을 바꾸는 예시이다.

 

 

 

'기타 > C' 카테고리의 다른 글

[C] 파일의 분할, 헤더파일  (0) 2020.06.17
[C] 매크로와 선행 처리기(Preprocessor)  (0) 2020.06.16
[C] 메모리 구조와 동적할당  (0) 2020.06.15
[C] 공용체(Union Type), 열거형(Enumerated Type)  (0) 2020.06.08
[C] 구조체  (0) 2020.06.01

 

 

공부했던 자료 정리하는 용도입니다.

재배포, 수정하지 마세요.

 

 


 파일의 분할 

  • static 선언
    * static 전역 변수
    * 함수의 static 변수
  • 헤더 파일
    * #include 지시자의 의미
    * 헤더 파일을 포함하는 방식
  • 구조체 정의
    * 헤더 파일의 중복 삽입 문제

 

파일의 분할

  소스코드 관리의 편의를 위해 각각의 파일에 용도 및 특성별로 함수와 변수를 나눠 저장한다.

 

#include <stdio.h>
int num = 0 ;

void Increment(void)
{
	num++;
}

int GetNum(void)
{
	return num;
}

int main(void)
{
	printf("num : %d \n", GetNum());
	Increment();
	printf("num : %d \n", GetNum());
	Increment();
	printf("num : %d \n", GetNum());
	
	return 0;
}

만약 위와 같은 코드가 있다고 가정하면 아래와 같이 파일을 3개로 분할해서 컴파일하는 것이 가능하다.

 

 

 

num.c

int num = 0 ;

func.c

extern int num;		// int형 변수 num이 외부에 선언되었다고 명시

void Increment(void)
{
	num++;
}

int GetNum(void)
{
	return num;
}

main.c

#include <stdio.h>
extern int num;			// int형 변수 num이 외부에 선언되었다고 명시
extern void Increment(void);	// 함수 void Increment(void)가 외부에 선언되었다고 명시
// void Increment(void)		// 함수가 외부에 선언된 경우는 extern선언 생략가능

int main(void)
{
	printf("num : %d \n", GetNum());
	Increment();
	printf("num : %d \n", GetNum());
	Increment();
	printf("num : %d \n", GetNum());
	
	return 0;
}

  이렇게 3개의 파일로 나눠서 컴파일을 하는 것도 가능하다. 그런데 컴파일러는 파일 단위로 컴파일을 진행하기 때문에 ``extern`` 키워드를 사용해서 변수나 함수가 외부에 선언 및 정의되었다고 명시해주어야 한다. 함수는 키워드를 생략하는 것이 가능하다.

 

 

 

 

Static 선언

  ■ static 전역 변수

  전역 변수에 ``static``을 선언하면 변수의 접근 범위가 파일 내부로 제한된다.(외부 파일에서 접근 불가)

 

 

  ■ 함수의 static 변수

  함수에도 ``static`` 변수를 선언할 수 있다. ``static`` 전역 변수와 마찬가지로 함수의 접근 범위가 파일 내부로 제한되는데, 이때는 @@extern@@ 선언을 하더라도 다른 파일에서는 접근이 불가능하다. 그래서 파일의 외부에서 원치 않게 호출되는 것을 막을 수 있다. 따라서 파일 내부에서만 호출하기 위해 정의된 함수라면 ``extern`` 선언을 추가하여 코드에 안전성을 높이는 것이 좋다.

 

 

 

 

헤더 파일

  외부에 선언된 변수에 접근하거나 외부에 정의된 함수를 호출하기 위한 선언들(``extern``으로 선언된 변수나 함수)을 헤더 파일에 모아놓고 헤더 파일을 포함시키면 더 효율적인 프로그램 제작이 가능하다. 선행 처리기와 매크로의 명령문도 파일 단위로만 유효하기 때문에 헤더 파일에 포함시키는 게 더 효율적인 경우도 있다.

 

 

  ■ #include 지시자의 의미

header1.h

{
	puts("Hello world!");

header2.h

	return 0;
}

main.c

#include <stdio.h>

int main(void)
#include "header1.h"
#include "header2.h"

``#include`` 지시자는 파일의 내용을 단순히 포함시키는 용도로 사용된다.

그래서 위와 같이 3개의 헤더 파일로 분리해서 실행해도 똑같은 결과가 나온다.

 

 

 

  ■ 헤더 파일을 포함하는 방식

#include <헤더파일 이름>			// 표준 헤더파일을 포함시키는 경우 사용
#include "[헤더파일 이름]또는 절대경로"	// 프로그래머가 정의한 헤더파일을 포함시킬때 사용
  1. ``<>``를 이용하는 방식
    : 표준 헤더 파일(기본적으로 제공되는 헤더 파일, 표준 C에 정의되어있다.)이 저장되어 있는 디렉터리에서 파일을 찾는다. 그래서 ``stdio.h``, ``stdlib.h`` ``string.h``와 같은 표준 헤더 파일을 포함시키는 경우에 사용된다.
  2. ``" "``를 이용하는 방식
    : 이 문장을 포함하는 소스파일이 저장된 디렉터리에서 헤더파일을 찾는다. 그래서 프로그래머가 정의한 헤더파일을 포함시키는 경우에 사용된다. 또한 헤더 파일의 이름뿐만 아니라, 드라이브 명과 디렉터리 경로를 포함하는 절대 경로나 상대 경로를 명시해서 헤더 파일을 지정하는 것도 가능하다.

 

   절대경로 

  • window 상의 절대경로 : ``\``로 구분
    ex) ``#include "C:\CPower\Myproject|header.h"``
  • Linux 상에서 절대경로 : ``/``로 구분
    ex) ``#include "/CPower/MyProject/header.h"``

 

   상대경로 

  상대경로를 기반으로 헤더파일을 지정하면 드라이브 명이나 디렉터리 위치에 영향을 덜 받는다.

  • ``../`` : 한 단계 상위 디렉터리를 의미
  • ``./`` : 현재 디렉터리를 의미

 

basicArith.h

// 함수 선언 (사칙연산 기능)
#define PI 3.1415
double Add(double num1, double num2);
double Min(double num1, double num2);
double Mul(double num1, double num2);
double Div(double num1, double num2);

basicArith.c

// 헤더파일에 선언했던 함수 정의 (사칙연산 기능)
double Add(double num1, double num2)
{
	return num1 + num2;
}
double Min(double num1, double num2)
{
	return num1 - num2;
}
double Mul(double num1, double num2)
{
	return num1 * num2;
}
double Div(double num1, double num2)
{
	return num1 / num2;
}

 

areaArith.h

// 함수 선언 (면적을 구하는 기능)
double TriangleArea(double base, double height);
double CircleArea(double rad);

areaArith.c

// 헤더파일에 선언했던 함수 정의 (면적을 구하는 기능)
#include "basicArith.h"	// basicArith.c에 정의되어있는 함수를 사용하기 때문에 포함시켜야함

double TriangleArea(double base, double height)
{
	return Div(Mul(base, height), 2);
}
double CircleArea(double rad)
{
	return Mul(Mul(rad, rad), PI);
}

 

roundArith.h

// 함수 선언 (둘레를 구하는 기능)
double RectangleRound(double base, double height);
double SquareRound(double side);

roundArith.c

// 헤더파일에 선언했던 함수 정의 (둘레를 구하는 기능)
#include "basicArith.h"	// basicArith.c에 정의되어있는 함수를 사용하기 때문에 포함시켜야함

double RectangleRound(double base, double height)
{
	return Mul(Add(base, height), 2);
}
double SquareRound(double side)
{
	return Mul(side, 4);
}

 

main.c

#include <stdio.h>
#include "areaArith.h"
#include "roundArith.h"

int main(void)
{
	printf("삼각형 넓이 (밑변 4, 높이 2) : %g \n", TriangleArea(4, 2));
	printf("원 넓이(반지름 3) : %g \n", CircleArea(3));
	
	printf("직사각형 둘레(밑변 2,5, 높이5.2) : %g \n", RectangleRound(2.5 ,5.2));
	printf("정사각형 둘레(변의 길이 3) : %g \n", SquareRound(3));
	
	return 0;
}

  헤더 파일을 활용하는 예시이다. 헤더파일을 사용하지 않으면 각각의 소스파일에서 호출하는 함수를 ``extern``으로 일일히 포함시켜야 하는데, 헤더파일을 만들었기 때문에 ``#include``문 하나만 사용하면 된다.

 

실행 결과

 

 

 

 

구조체 정의

  구조체의 선언 및 정의는 헤더 파일에 삽입하는 것이 좋다. 그러나 하나의 소스파일 내에서만 사용되는 구조체라면 소스파일에 정의하는 것도 나쁘지 않다.

 

 

  ■ 헤더 파일의 중복삽입 문제

  구조체의 정의는 실행파일의 내용에 직접적인 연관이 있는 정보이므로 구조체의 정의가 포함된 헤더파일의 포함관계를 잘못 설정하면 구조체가 두 번 정의된 형태가 되어 컴파일 에러가 발생한다. 그래서 조건부 컴파일을 활용해서 중복 삽입 문제를 해결한다.

 

 

stdiv.h

#ifndef __STDIV_H__	// __STDIV_H__라는 이름의 매크로가 정의되지 않으면
#define __STDIV_H__

typedef struct div
{
	int quotient;
	int remainder;
}Div;

#endif

 

intdiv.h

#ifndef __INTDIV_H__	// __INTDIV_H__라는 이름의 매크로가 정의되지 않으면
#define __INTDIV_H__

#include "stdiv.h"
Div IntDiv(int num1, int num2);

#endif

intdiv.c

#include "stdiv.h"

Div IntDiv(int num1, int num2)
{
	Div dval;
	dval.quotient = num1 / num2;
	dval.remainder = num1 % num2;
	
	return dval;
}

 

main.c

#include <stdio.h>
#include "intdiv.h"
#include "stdiv.h"

int main(void)
{
	Div val = IntDiv(5, 2);
	printf("몫 : %d \n", val.quotient);
	printf("나머지 : %d \n", val.remainder);
	
	return 0;
}

``#ifndef ~ #endif``를 사용해서 중복 삽입 문제를 방지하는 예시이다.

 

실행 결과

 

 

 

'기타 > C' 카테고리의 다른 글

[C] 비트연산자  (0) 2020.11.07
[C] 매크로와 선행 처리기(Preprocessor)  (0) 2020.06.16
[C] 메모리 구조와 동적할당  (0) 2020.06.15
[C] 공용체(Union Type), 열거형(Enumerated Type)  (0) 2020.06.08
[C] 구조체  (0) 2020.06.01

 

 

공부했던 자료 정리하는 용도입니다.

재배포, 수정하지 마세요.

 

 


 매크로와 선행 처리기 

  • 선행 처리 명령문
    * define : Object-like macro
    * define : Function-like macro
    - 매크로를 두 줄에 걸쳐 정의하는 경우
    - 매크로 정의 시 먼저 정의된 매크로 사용 가능
  • 조건부 컴파일(Conditional Compilation)을 위한 매크로
    * #if... #endif : 참이라면 코드 삽입
    * #ifdef... #endif : 정의되었다면 삽입
    * #ifndef... #endif : 정의되지 않았다면 삽입
    * (#if, #ifdef, #ifndef)의 #else
    * (#if)의 #elif
  • 매개변수의 결합과 문자열화
    * 문자열 내에서 매크로의 매개변수 치환 : # 연산자
    * 필요한 형태대로 단순 결합 : 매크로 # # 연산자

 


 

선행 처리기

실행파일 생성 과정

  위의 그림에서 선행 처리는 선행 처리기에 의해, 컴파일은 컴파일러에 의해, 링크는 링커에 의해 진행된다. 컴파일 이전에 소스파일은 선행 처리과정을 거치는데 이 과정은 프로그래머가 삽입해 놓은 선행 처리 명령문대로 소스코드의 일부를 수정(대부분 단순 치환(substitution)하는 형태)하는 작업이다. (선행 처리기에 의해 변환되는 과정 자체를 가리켜 매크로 확장(macro expanision)이라고 한다.) 또한 선행 처리해도 여전히 소스파일이며, 컴파일 과정을 거쳐야 바이너리 데이터로 이루어진 오브젝트 파일이 생성된다.

###include<stdio.h>##도 ## # ##문자로 시작하는 선행 처리 명령문이다. 따라서 직접 ##stdio.h##파일을 열어 그 안에 있는 내용을 옮겨도 같은 결과가 나온다.

 

 

선행 처리 명령문

  ■ define: Object-like macro

#define [매크로] [매크로 몸체]

// 사용예시
#define PI 3.1415

  매크로 자체가 숫자나, 단순 값 등을 의미하는 등의 경우 오브젝트와 유사한 매크로(object-like macro) 또는 간단히 매크로 상수라고 한다. 매크로의 이름은 대문자로 정의해서 매크로라는 사실을 부각시키는 것이 일반적이다.

 

  • ``#define`` : 지시자라고 한다. 선행 처리기가 이 부분을 보고 프로그래머가 지시하는 바를 파악한다.
  • 매크로 : #define 지시자 뒤에 등장하는 것들을 매크로라고 부른다.
  • 매크로 몸체(대체 리스트) : 매크로를 매크로 몸체로 치환한다.

 

#include <stdio.h>

#define NAME "홍길동"	// NAME이 문자열 "홍길동"으로 대체
#define AGE	24	// AGE가 24로 대체
#define PRINT_ADDR puts("주소 : 경기도 용인시\n"); // PRINT_ADDR가 함수호출문으로 대체

int main(void)
{
	printf("이름 : %s \n", NAME);
	printf("나이 : %d \n", AGE);
	PRINT_ADDR;
	
	return 0;
}

매크로 상수를 사용하는 예시이다.

 

실행 결과

 

 

 

  ■ define: Function-like macro

#define SQUARE(X) X * X		// SQUARE(X)가 X * X로 대체
#define SQUARE(X) ((X) * (X));	// 각 전달인자를 괄호()로 묶고, 전체도 괄호로 묶어 주는 것이 좋다.

  매개변수가 존재하는 매크로도 정의 가능하다. 동작 방식이 함수와 유사하여 함수와 유사한 매크로(function-like macro)라고 하고 간단히 매크로 함수라고도 한다. 아래와 같은 장단점이 있기 때문에 보통 작은 크기의 함수나 호출의 빈도수가 높은 함수를 매크로로 정의한다.

  위의 예시의 경우 ``SQUARE(X)``와 동일한 패턴을 만나면 무조건 ``X * X``로 치환하는데, 치환이 의도한 바와 다르게 되어 다른 값이 출력된다. 매크로에 몸체 부분을 구성하는 X와 같은 전달 인자 하나하나에 괄호를 하고, 전체를 괄호로 한번 더 묶어주는 것이 좋다.

 

   매크로 함수의 장점 

  • 일반 함수에 비해 실행 속도가 빠르다.
    매크로 함수는 선행 처리에 의해 매크로 함수의 몸체 부분이 매크로 함수의 호출 문장을 대신하기 때문에 실행 속도상의 이점이 있다.
  • 자료형에 따라서 별도로 함수를 정의하지 않아도 된다.
    전달 인자의 자료형에 상관없이 제대로 치환되기 때문이다.

   매크로 함수의 단점 

  • 정의하기 까다롭다
  • 디버깅하기 쉽지 않다.
    매크로를 잘못 정의할 경우 에러 메시지는 선행 처리 이후의 소스파일을 기준으로 출력되기 때문에 일반적인 에러 메시지보다 이해하기 힘들다.

 

#include <stdio.h>
#define SQUARE(X) X * X

int main(void)
{
	int num = 20;
	
	// 정상적 결과 출력
	printf("Square of num : %d \n", SQUARE(num)); 
	printf("Square of -5 : %d \n", SQUARE(-5));
	printf("Square of 2.5 : %g \n", SQUARE(2.5));
	
	// 비정상적 결과 출력
	printf("Square of 3 + 2 : %d \n", SQUARE(3 + 2)); 
	
	return 0;
}

매크로 함수를 사용하는 예시이다. 괄호로 각 매개변수와 전체를 묶지 않으면 ``SQUARE(3 + 2)``가 ``3 + 2 * 3 + 2`` 가되버려서 결과값이 11이 나와버린다. @@#define SQUARE(X) ((X) * (Y))@@로 선언해야 의도하는 결과가 나온다.

 

실행결과

 

 

 

  + 매크로를 두 줄에 걸쳐 정의하는 경우

#define SQUARE(X)	\
((X) * (X))

정의하는 매크로가 길어지는 경우 가독성을 높이기 위해 두 줄에 걸쳐 매크로를 정의하기도 한다.

``\``문자를 활용해서 줄이 바뀜을 명시한다.

 

 

 

  + 매크로 정의 시 먼저 정의된 매크로 사용 가능

#include <stdio.h>
#define PI 3.14
#define PRODUCT(X, Y) ((X) * (Y))
#define CIRCLE_AREA(R) (PRODUCT((R), (R)) * PI)	// 먼저 정의된 PRODUCT(X, Y) 사용가능

int main(void)
{
	double rad = 2.1;
	printf("반지름이 %g인 원의 넓이 : %g \n", rad, CIRCLE_AREA(rad));
	
	return 0;
}

먼저 정의된 매크로는 뒤에서 매크로를 정의할 때 사용 가능하다.

 

실행 결과

 

 

 

 

조건부 컴파일(Conditional Compilation)을 위한 매크로

  매크로 지시자 중에는 특정 조건에 따라 소스코드의 일부를 삽입하거나 삭제할 수 있도록 디자인된 지시자가 있다.

 

 

  ■ #if... #endif : 참이라면 코드 삽입

  조건부 코드 삽입을 위한 지시자이다. ``#if``문 뒤에는 반드시 ``#endif``문이 등장해야 하고, 두 지시자 사이에 존재하는 코드는 조건에 따라서 삽입 및 삭제가 된다.

 

#include <stdio.h>
#define ADD 1
#define MIN 0

int main(void)
{
	int num1, num2;
	printf("두 개의 정수 입력 : ");
	scanf("%d %d", &num1, &num2);
	
#if ADD	// ADD가 참이면
	printf("%d + %d = %d \n", num1 ,num2 ,num1 + num2);
#endif

#if MIN	// MIN이 참이면
	printf("%d - %d = %d \n", num1, num2, num1 - num2);
#endif
	
	return 0;
}

  ``#if ... #endif``를 사용하는 예시이다.

``ADD``가 ``1``이기 때문에 ``if ADD``와 ``#endif``사이에 있는 코드가 삽입되고,

``if MIN``과 ``#endif``사이의 코드는 삭제되어 결과적으로 두 정수의 덧셈이 일어난다.

 

실행 결과

 

 

 

  ■ #ifdef... #endif : 정의되었다면 삽입

  ``#ifdef``는 매크로가 정의되었느냐, 정의되지 않았느냐에 따라를 기준으로 동작한다.

 

#include <stdio.h>
// 선언했느냐가 중요하고 매크로의 값은 중요하지 않기 때문에 생략해서 정의해도 된다.(선행처리 과정에서 공백으로 대체됨(소멸됨))
// #define ADD 1
#define MIN 0

int main(void)
{
	int num1, num2;
	printf("두 개의 정수 입력 : ");
	scanf("%d %d", &num1, &num2);
	
#ifdef ADD	// 매크로 ADD가 정의되었다면
	printf("%d + %d = %d \n", num1 ,num2 ,num1 + num2);
#endif

#ifdef MIN	// 매크로 MIN이 정의되었다면 
	printf("%d - %d = %d \n", num1, num2, num1 - num2);
#endif
	
	return 0;
}

  ``#ifdef ... #endif``를 사용하는 예시이다.

``ADD``가 정의되었으면 ``#ifdef ADD``와 ``#endif``사이의 코드가 삽입된다.

 ``MIN``은 정의되지 않았기 때문에 ``#ifdef MIN``과 ``#endif``사이의 코드는 삭제된다.

 

실행 결과

 

 

 

  ■ #ifndef... #endif : 정의되지 않았다면 삽입

  ``#ifdef ... #endif``와 반대로 정의되지 않았으면 삽입된다. 헤더 파일의 중복 포함을 막기 위해 주로 사용된다.

 

 

  ■ (#if, #ifdef, #ifndef)의 #else

  ``#if``와 ``#ifdef``, ``#ifndef``문에 ``#else``문을 추가할 수 있다. 의미는 반복문 ``if``와 비슷하다.

 

#include <stdio.h>
#define HIT_NUM 5 

int main(void)
{
#if HIT_NUM == 5
	puts("매크로 상수 HIT_NUM은 현재 5입니다.");
#else
	puts("매크로 상수 HIT_NUM은 현재 5가 아닙니다.");
#endif

	return 0;
}

``#else``를 사용하는 예시이다. 

 

실행 결과

 

 

 

  ■ (#if)의 #elif

  ``#if``문에도 ``#elif``를 여러 번 추가할 수 있고, ``#elif``의 끝을 ``#else``로 마무리할 수 있다.

 

#include <stdio.h>
#define HIT_NUM 7

int main(void)
{
#if HIT_NUM == 5
	puts("매크로 상수 HIT_NUM은 현재 5입니다.");
#elif HIT_NUM == 6	
	puts("매크로 상수 HIT_NUM은 현재 6입니다.");
#elif HIT_NUM == 7
	puts("매크로 상수 HIT_NUM은 현재 7입니다.");
#else
	puts("매크로 상수 HIT_NUM은 5, 6, 7은 확실히 아닙니다.");
#endif
	
	return 0;
}

``#elif``를 사용하는 예시이다.

 

실행 결과

 

 

 

 

매개변수의 결합과 문자열화

  ■ 문자열 내에서 매크로의 매개변수 치환 : # 연산자

#define STR(ABC)	#ABC	// 매개변수 ABC에 전달되는 인자를 문자열 ABC로 치환
STR(123)		// "123"으로 치환
STR(12, 23, 34)	// "12, 23, 34"로 치환

// 문자열을 나란히 선언하면 하나의 문자열로 간주된다.
char * str = "ABC" "DEF";	// char * str = "ABCDEF"와 동일
char * str = STR(12) STR(34);	// char * str = "12" "34";로 치환되어 char * str = "1234";와 동일해짐

``#``연산자는 치환의 결과를 문자열로 구성하는 연산자이다. 나란히 선언하면 하나의 문자열로 간주된다. 

 

#include <stdio.h>
#define STRING_JOB(A, B) #A "의 직업은 " #B "입니다."

int main(void)
{
	printf("%s \n", STRING_JOB(이동춘, 나무꾼));
	printf("%s \n", STRING_JOB(한상순, 사냥꾼)); 
	
	return 0;
}

``#``연산자를 이용해서 매개변수를 치환하는 예시이다.

 

실행 결과

 

 

 

  ■ 필요한 형태대로 단순 결합 : 매크로 # #연산자(붙여 써야 함)

  ``# #`` 연산자는 매크로 함수의 전달 인자를 다른 대상(전달 인자, 숫자, 문자, 문자열등)과 이어 줄 때 사용한다.

(코드블럭 명령어로 지정해놔서 띄어놨는데 원래 #끼리 붙여 써야 맞는 표현이다... ㅜㅜ)

 

#include <stdio.h>
// #define STNUM(Y, S, P) YSP
#define STNUM(Y, S, P) Y # # S # # P	// Y와 S, P를 단순연결

int main(void)
{
	printf("%d \n", STNUM(10, 65, 175));
	printf("%d \n", STNUM(10, 65, 075));
	
	return 0;
}

``# #``연산자를 사용해서 매크로의 전달 인자를 단순 결합하는 예시이다.

 

실행 결과

 

 

 

'기타 > C' 카테고리의 다른 글

[C] 비트연산자  (0) 2020.11.07
[C] 파일의 분할, 헤더파일  (0) 2020.06.17
[C] 메모리 구조와 동적할당  (0) 2020.06.15
[C] 공용체(Union Type), 열거형(Enumerated Type)  (0) 2020.06.08
[C] 구조체  (0) 2020.06.01

 

 

공부했던 자료 정리하는 용도입니다.

재배포, 수정하지 마세요.

 

 


 메모리 구조 

  • 코드 영역(Code Area)
  • 데이터 영역(Data Area)
  • 스택 영역(Stack Area)
  • 힙 영역(Heap Area)

 

 동적 할당 

  • 힙 영역 메모리 공간 할당과 해제
    * malloc
    * calloc
    * 할당된 메모리 공간 해제 : free
       - 포인터 증가, 감소 연산과 메모리 해제

    * 힙에 할당된 메모리 공간 확장 : realloc
  • memset 함수

 


 

메모리 구조

  프로그램 실행 시 운영체제에 의해 마련되는 메모리의 구조는 다음과 같이 네 개의 영역으로 구분이 된다.

 

  • 코드 영역(Code Area)
    실행할 프로그램의 코드가 저장되는 메모리 공간. CPU는 코드 영역에 저장된 명령문들을 하나씩 가져가서 실행한다.
  • 데이터 영역(Data Area)
    전역 변수와 static 변수가 할당된다. 프로그램의 시작과 동시에 메모리에 할당되어 프로그램 종료 시까지 남아있다. main 함수가 호출되기 이전에 데이터 영역이 먼저 초기화되고, (return문이 실행되어) 프로그램이 종료된 이후에 운영체제에 의해 할당된 메모리 공간 전체를 반환하는데, 이때가 전역 변수가 소멸하는 시점이다.
  • 스택 영역(Stack Area)
    지역변수와 매개변수가 할당된다. 함수를 빠져나가면 소멸한다.
  • 힙 영역(Heap Area)
    ``malloc``, ``calloc``, ``realloc``함수를 통해 동적 할당되는 메모리들이 있는 공간

 

 

 

 

동적 할당(dynamic allocation)

  ``malloc``, ``calloc``, ``realloc``함수의 호출을 통한 메모리 공간의 할당을 가리켜 동적 할당이라고 한다. 메모리 크기를 컴파일러가 결정하지 않고, 프로그램의 실행 중간에 호출되는 함수가 결정하기 때문이다.

 

 

힙 영역의 메모리 공간 할당과 해제

  힙 영역에 할당하는 메모리는 프로그래머가 원하는 시점에 할당하고 소멸시킬 수 있다. 동적 메모리 할당 (dynamic memory allocation)이라고 부른다. 메모리와 관련된 함수는 `stdlib.h`헤더에 선언되어 있다.

 

 

  ■ malloc

#include <stdlib.h>
void * malloc(size_t size)	// size(byte)크기의 메모리 공간을 힙 영역에 할당
				// 성공하면 메모리 주소를 반환, 실패하면 NULL을 반환

// 사용예시(주소값을 반환하기 때문에 포인터 변수로 접근해야한다.)
void * ptr = (int *)malloc(sizeof(int));	// 포인터변수 ptr이 malloc함수를 통해 할당된 메모리의 첫번째 byte를 가리킴
free(ptr)	// 사용한 뒤 꼭 메모리를 해제해야한다.
// malloc함수는 메모리 할당에 실패할 경우 NULL을 반환하기 때문에 if문으로 함수의 호출 여부를 확인하기도 한다.
int * ptr = (int *)malloc(sizeof(int));
if(ptr == NULL)
{
	// 메모리 할당 실패에 따른 오류의 처리
}

  인자로 전달된 정수값에 해당하는 byte크기의 메모리 공간을 힙 영역에 할당한다. 함수 호출에 성공하면 할당된 메모리의 주소 값을, 실패하면 ##NULL##을 반환한다. @@malloc@@함수로 할당된 메모리는 프로그래머가 직접 @@free@@ 함수의 호출을 통해 해제하지 않으면 메모리에 계속 남아있기 때문에 주의한다.

 

  • 주소 값을 반환하기 때문에 포인터 변수를 사용해야 하는데, 이렇게 하면 해당 포인터 변수가 ``malloc`` 함수 호출로 인해 할당된 메모리의 첫 번째 byte를 가리키게 된다.
  • 함수의 반환형이 @@void@@형 포인터기 때문에 주소값을 형변환해야 메모리에 접근할 수 있다.
    ``void * ptr = malloc(sizeof(int))`` : 할당할 메모리 크기는 알지만 몇 byte씩 접근해야 하는지 모르기 때문에 접근 불가
    ##int * ptr = (int *)malloc(sizeof(int));##
    ##double * ptr = (double *)malloc(sizeof(double) * 7);##
  • @@malloc@@함수와 @@free@@함수의 호출 위치 및 시점에는 제한이 없다.
  • @@calloc@@함수와 다르게 할당된 메모리 공간을 별도의 값으로 초기화하지 않는다.

 

 

 

  ■ calloc

#include <stdlib.h>
void * calloc(size_t elt_count, size_t elt_size);

``malloc`` 함수와 다르게 인자가 두 개이고, 할당된 메모리 공간의 모든 비트를 @@0@@으로 초기화시킨다.

함수 호출에 성공하면 할당된 메모리의 주소 값을, 실패하면 ##NULL##을 반환한다.

 

  • ``elt_count`` : 할당할 블록의 개수 정보
  • ``elt_size`` : 블록 하나당 byte 크기의 정보

 

 

 

  ■ 할당된 메모리 공간 해제 : free

#include <stdlib.h>
void free(void *_Block);	// 힙 영역에 할당된 메모리 공간 해제

free(포인터);

  변수는 스택(stack)에 생성되며 malloc 함수는 힙(heap) 부분의 메모리를 사용한다. 스택에 생성된 변수와 달리 힙에서 할당한 메모리는 반드시 해제를 해주어야 한다. 메모리를 해제하지 않아서 메모리 사용량이 계속 증가하는 현상을 메모리 누수(memory leak)라 부른다.

 

  - 포인터 증가, 감소 연산과 메모리 해제

  동적 메모리를 할당받은 포인터를 ``++``. ``--``포인터연산을 하게되면 포인터에 저장된 메모리 주소 자체가 바뀌게 된다.  이때 free 함수에서 메모리 주소가 바뀐 포인터로 메모리 해제를 하면 에러가 발생하므로 주의한다. (free 함수로 메모리 해제를 할 때는 반드시 처음에 메모리를 할당할 때 받은 주소를 넣어야한다.)

 

 

 

 

  ■ 힙에 할당된 메모리 공간 확장 : realloc

#include <stdlib.h>
void * realloc(void * ptr, size_t size);

// 사용예시
arr = (int *)realloc(arr, sizeof(int) * 5);	// 길이가 5인 int형 배열로 확장

힙에 할당된 메모리 공간을 확장할 때 사용하는 함수이다.

함수 호출 성공 시 새로 할당된 메모리 주소 값을, 실패하면 ##NULL##을 반환한다.

 

  • ``ptr`` : 확장하고자 하는 힙 메모리의 시작 주소 값
  • ``size`` : 확장하고자 하는 메모리의 전체 크기

 

   realloc을 사용했을 때 발생하는 경우의 수 

  • 기존에 할당된 메모리 공간의 뒤에 확장할 영역이 넉넉한 경우
    malloc 함수가 반환한 주소 값과 realloc 함수가 반환한 주소 값이 같다.
  • 기존에 할당된 메모리 공간의 뒤에 확장할 영역이 부족한 경우
    malloc 함수가 반환한 주소 값과 realloc 함수가 반환한 주소 값이 다르다.

 

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
	int * ptr1 = (int *)malloc(sizeof(int));	// 포인터변수 ptr1이 malloc함수를 통해 할당된 메모리의 첫번째 byte를 가리킴
	int * ptr2 = calloc(5, sizeof(int));		// calloc으로 4byte * 5 크기의 동적메모리 할당 
	int i;
	
	ptr2 = realloc(ptr2, sizeof(int) * 7);		// ptr2가 가리키는 메모리를 길이가 7인 int형 배열로 확장 
	
	*ptr1 = 20;
	for(i = 0 ; i < 7 ; i++)
		ptr2[i] = i + 1;
	
	printf("%d \n", *ptr1);
	for(i = 0 ; i < 7 ; i++)
		printf("%d ", ptr2[i]);
	
	free(ptr1);
	free(ptr2);
	
	return 0;
}

동적 할당에 관련된 함수를 사용하는 예시이다.

6행의 ``malloc``함수는 반환하는 주소 값을 ``int *``형으로 형 변환했기 때문에 메모리에 접근할 수 있다.

 

실행 결과

 

 

 

 

 


memset (memory set)

memset(포인터, 설정할 값, 크기);
void *memset(void *_Dst, int _Val, size_t _Size);
// 값 설정이 끝난 포인터를 반환


// 사용 예시
long long *numPtr = malloc(sizeof(long long));

memset(numPtr, 0, sizeof(long long));	// numPtr이 가리키는 메모리의 long long 크기만큼 0으로 설정
  • 메모리의 내용을 원하는 크기만큼 특정값으로 설정할 수 있다.
  • string.h헤더에 선언되어 있다. (memory.h헤더를 포함해도 사용가능) 
  • 메모리의 내용을 모두 0으로 만들 때 주로 사용한다.

 

 

 

'기타 > C' 카테고리의 다른 글

[C] 파일의 분할, 헤더파일  (0) 2020.06.17
[C] 매크로와 선행 처리기(Preprocessor)  (0) 2020.06.16
[C] 공용체(Union Type), 열거형(Enumerated Type)  (0) 2020.06.08
[C] 구조체  (0) 2020.06.01
[C] 문자, 문자열  (0) 2020.05.28

 

 

공부했던 자료 정리하는 용도입니다.

재배포, 수정하지 마세요.

 

 

 

 

공용체(Union Type)

  공용체는 ``union`` 키워드를 사용해서 선언하며, 구조체와 선언방식은 비슷하지만 메모리에 할당되는 방식이 다르다. 구조체는 각각의 멤버가 메모리에 할당되지만 공용체는 멤버가 각각 할당되지 않고 크기가 가장 큰 멤버의 변수만 하나 할당되어 이를 공유하게 된다. 하나의 메모리 공간을 둘 이상의 방식으로 접근할 때 사용한다.

 

#include <stdio.h>

// 구조체
typedef struct sbox
{
	int mem1;
	int mem2;
	double mem3;
} Sbox;

// 공용체 
typedef union ubox
{
	int mem1;
	int mem2;
	double mem3;	
} Ubox;

int main(void)
{
	Sbox sbx;
	Ubox ubx;
	
	printf("구조체 : %p %p %p \n", &sbx.mem1, &sbx.mem2, &sbx.mem3);
	printf("공용체 : %p %p %p \n", &ubx.mem1, &ubx.mem2, &ubx.mem3);
	printf("Sbox size : %d\nUbox size : %d \n", sizeof(Sbox), sizeof(Ubox));
	
	return 0;
}

 

실행 결과

구조체는 각 멤버의 크기만큼 주소 값이 증가하는 반면,

공용체는 주소 값이 모두 같은 것을 확인할 수 있다.

 

구조체 변수와 공용체 변수의 차이점

그림으로 표현하면 위와 같다.

 

#include <stdio.h>

// 구조체 dbshort(DBShort) 정의 
typedef struct dbshort
{
	unsigned short upper;
	unsigned short lower;
} DBShort;

// 공용체 rdbuf(RDBuf) 정의 
typedef union rdbuf
{
	int iBuf;	 
	char bBuf[4];
	DBShort sBuf;	// DBShort형 구조체 변수를 멤버로 갖는다. 
} RDBuf;

int main(void)
{
	RDBuf buf;
	printf("정수 입력 : ");
	scanf("%d", &(buf.iBuf));
	
	printf("상위 2byte : %u \n", buf.sBuf.upper);
	printf("하위 2byte : %u \n", buf.sBuf.lower);
	printf("상위 1byte의 아스키코드 : %c \n", buf.bBuf[0]);
	printf("하위 1byte의 아스키코드 : %c \n", buf.bBuf[3]);
	
	return 0;
}

공용체의 특성을 이용해서 ``int``형 정수를 여러 가지 방식으로 접근하는 예시이다.

 

실행 결과
공용체 변수의 할당과 공유

공용체 변수 ``buf``는 메모리에서 위와 같은 형태로 할당되고 공유된다.

 

 

 

 

 


열거형(Enumerated Type)

enum syllable
{
	Do = 1, Re = 2, Mi = 3, Fa = 4, So = 5, La = 6, Ti = 7;
};

// 자료형을 생략한 형태로 열거형을 정의할 수도 있다.
enum {Do = 1, Re = 2, Mi = 3, Fa = 4, So = 5, La = 6, Ti = 7};

// 상수 값이 없을 경우 0부터 시작해서 1씩 증가하는 형태로 결정된다.(아래 두 문장은 같은 의미)
enum color {RED, BLUE, WHITE, BLACK};	
enum color {RED = 0, BLUE = 1, WHITE = 2, BLACK = 3};

// 중간에 값이 없으면 처음 값에서 1씩 증가한 값이 된다. (아래 두 문장은 같은 의미)
enum color {RED = 3, BLUE, WHITE = 6, BLACK};
enum color {RED = 3, BLUE = 4, WHITE = 6, BLACK = 7};

  열거형은 ``enum``키워드를 사용해서 선언하며, 구조체나 공용체와 마찬가지로 자료형을 정의하는 방법으로 사용된다. 구조체와 공용체는 자료형의 선언을 통해 멤버에 저장할 '값의 유형'을 결정하였다면 열거형의 경우에는 저장이 가능한 '값 자체'를 정수의 형태로 결정한다. 둘 이상의 연관있는 이름을 상수로 선언함으로써 프로그램의 가독성을 높이는 목적으로 사용된다. 변수의 선언이 목적이 아닌 상황에서는 자료형의 이름을 생략한 형태로 열거형을 정의하기도 하는데, 이렇게 하면 열거형 상수는 상수로써 의미를 지닌다.

 

  만약 위와 같이 ``syllable``이라는 열거형을 정의한다면 ``Do``, ``Re``, ``Mi``, ``Fa``, ``So``, ``La``, ``Ti``라는 이름의 상수를 각각 ``1``, ``2``, ``3``, ``4``, ``5``, ``6``, ``7``로 정의하고, 이 값들을 ``syllable``형 변수가 저장할 수 있는 값들로 제한한 것이다.

 

 

#include <stdio.h>

// 열거형 syllable(Syllable) 정의 
typedef enum syllable	// typedef 선언으로 enum syllable의 새로운 이름 Syllable 부여 
{
	Do = 1, Re = 2, Mi = 3, Fa = 4, So = 5, La = 6, Ti = 7
} Syllable;

void Sound(Syllable sy)
{
	switch(sy)
	{
		case Do:	// case 1: 도 가능 
			puts("도 입니다.");
			return;
		case Re:	// case 2: 도 가능
			puts("레 입니다.");
			return;
		case Mi:	// case 3: 도 가능
			puts("미 입니다.");
			return;
		case Fa:	// case 4: 도 가능
			puts("파 입니다.");
			return;
		case So:	// case 5: 도 가능
			puts("솔 입니다.");
			return;
		case La:	// case 6: 도 가능
			puts("라 입니다.");
			return;
		case Ti:	// case 7: 도 가능
			puts("시 입니다.");
			return;
	}
} 

int main(void)
{
	Syllable tone;	// 열거형인 Syllable형인 변수 tone 선언 
	for(tone = Do; tone <= Ti ; tone += 1)
		Sound(tone);
	
	return 0;
}

  열거형을 정의하고 사용하는 예시이다. 열거형 상수(enumeration constants)들은 ``int``형으로 표현되는 상수이기 때문에 주석부분처럼 ``case`` 레이블에 상수 ``Do``, ``Re``, ...을 대신해서 ``1``, ``2``, ...을 삽입해도 결과는 같다. 위의 예시의 경우 각각의 값이 음계와 관련된 것이기 때문에 열거형을 이용해서 프로그램의 가독성을 높인 것이다.

 

실행 결과

 

 

 

'기타 > C' 카테고리의 다른 글

[C] 매크로와 선행 처리기(Preprocessor)  (0) 2020.06.16
[C] 메모리 구조와 동적할당  (0) 2020.06.15
[C] 구조체  (0) 2020.06.01
[C] 문자, 문자열  (0) 2020.05.28
[C] 스트림, 버퍼  (0) 2020.05.27

+ Recent posts