*주의 - 개인적으로 학습한 내용이므로 틀린 부분을 알려주시면 진심으로 감사드리겠습니다.
1. 선행처리기
선행처리기는 compiler 가 본격적으로 compile 하기 전에 header file과 source 파일을 미리 읽어와 선언해주고, 선행처리기의 연산자로 사용한 문장들을 미리 처리해준다.
이게 무슨 소용이냐 싶겠지만, compiler 는 딱 주어진 문장만 compile하여 assembly 언어로 바꿔주기 때문에 compiler가 주어진 문장을 번역할 수 있게 preprocessor 가 준비해준다고 생각하면 편하다.
선행처리기의 연산자들은 #을 사용하여 표기한다.
가장 먼저 우리가 계속해서 사용해오던 #include가 있다.
1-1. #include
include 는 preprocessor에게 해당하는 파일을 복사해서 붙여넣으라는 뜻이다.
복사해서 붙여넣기 때문에 필요한 모든 파일에 선언을 해줘야 한다.
가령 #include <stdio.h> 는 stdio라는 header file을 그 자리에 붙여넣어 선언해달라는 뜻이다.
stdio.h 는 standard input output으로 input과 output을 담당하는 함수들을 가지고 있다.
<> 기호는 해당 언어에 대한 툴을 설치할 때 같이 설치되는 표준 라이브러리 폴더에서 파일을 찾아 불러오라는 뜻이고,
"" 기호는 현재 프로그램이 실행중인 폴더에서 먼저 찾고, 없을 경우 표준 라이브러리 폴더에서 찾으라는 뜻이다.
""는 그 파일이 있는 경로를 직접 설정이 가능하다. 예를 들어 asdf.h파일이 있을 경우
#include "../asdf.h"
처럼 설정할 수 있다는 뜻이다. 여기서 ../ 는 현재 프로그램이 실행중인 폴더 바로 상위의 폴더를 뜻하며 ./의 경우 현재 폴더를 뜻한다.
1-2. #define
define은 말 그대로 정의한다는 뜻이다.
주로 상수를 고정시키거나 매크로를 만드는데 사용된다.
매크로는 편하게 사용하기 위해 자동으로 완성되는 형식을 말하는데, 선행처리기는 define된 단어들을 전부 미리 선언한 것으로 치환한다.
예를 들어, #define a 30 을 선언할 경우, 본문에 a가 단독으로 쓰인 경우는 모두 30으로 치환해버린다.
int main(){
printf("%d", a);
}
가 있을 경우 printf("%d", 30); 으로 바꿔버린다. 그렇기 때문에 define 문에 ;를 써야할지 말아야할지는 자신이 잘 조절해서 결정해야한다.
define은 함수와 달리 호출의 형식이 아니고, compile 이전에 수행되는 치환 방식이기 때문에 많이 쓸 수록 코드가 길어진다는 단점이 있으며, 반대로 호출의 형식이 아니기 때문에 수행속도가 빨라진다는 장점이 있다.
게다가 많이 쓰는 문법을 매크로로 만들어놓으면 굉장히 편하다.
예를 들어, #define print(x) printf("%d", x) 로 선언해 놓으면 하나의 정수를 출력할 때 print(x)만 쓰면 사용할 수 있다는 뜻이다.
조금 주의할 점은 #define sqr(x) x*x 로 선언할 경우 sqr(3+5)를 쓸 경우 3+5*3+5가 그대로 복사되기 때문에 원래 의도한 64가 아닌 23이 나오는 이상한 결과를 얻을 수 있다. 그렇기 때문에 항상 대입되는 수는 괄호로 묶어서 써주는게 좋다. sqr(x) ((x)*(x)) 같은 형식으로.
그리고 여러 문장도 한 번에 선언할 수 있다. 예를 들어 자주 쓰이는 swap의 경우
#define swap(a,b) int tmp = *(a); *(a) = *(b); *(b) = tmp;로 선언할 경우
integer type a,b를 swap(a,b) 만으로 바꿀 수 있게 된다.
예전에는 {}로 묶어야만 변수선언을 할 수 있었지만, 요즘에는 그냥 된다.
또 변수 선언할 때 귀찮도록 긴 타입들을 줄여서 쓸 수도 있다.
#define ll long long 을 쓸 경우 long long 타입의 변수를 ll a; 로 선언할 수도 있다.
cf) typedef
typedef는 앞선 define 의 활용 방법 중 타입을 줄여쓰는 것과 비슷하다.
#define ll long long 과 비슷하게 typedef long long ll; 로 활용할 수 있다.
두 가지는 얼핏 비슷해 보이지만, typedef의 경우 선행처리되지 않는다는 점이 다르기 때문에 선언 끝에 ;를 붙여야하는등 아예 영역이 다르다.
거기다가 typedef 는 매크로가 아니기 때문에 치환 되는 것과는 달리 타입에 별명을 붙이는 일을 한다.
결정적으로 다른 점은 한 번만 선언되어야 하는 타입에는 #define은 쓰일 수 없고 typedef는 쓰일 수 있다는 것이다.
예를 들어 struct 를 각 방식으로 만들어보자.
#define tag struct tag1 { int a; char c[20]; float b;}
typedef struct tag1 {int a; char c[20]; float b;} tag;
각각은 처음 선언시에는 동일하다.
tag x = {1,"ABC", 3.14};
tag x = {1,"ABC",3.14};
하지만 다른 형식을 사용할 때 문제가 발생한다.
tag * p = &x;
만약 tag 를 define으로 치환한다면 struct tag1{} * p = &x; 가 되기 때문에 같은 태그의 다른 타입(포인터 타입으로 바뀜)으로 새로 선언되기 때문에 재정의의 오류가 발생하게 된다.
반면 typedef는 struct tag1을 tag라고 부르겠다는 별명을 정한 것이므로 tag * p 는 struct tag1 타입을 가리키는 포인터 타입으로 인식이 된다.
1-3. #, ## 연산자
#연산자는 string처럼 인식되도록 바꿔준다.
가령 #define prt(x) printf("asdf" x)로 할 경우 x에 string 타입을 넣지 않는 이상 에러가 날 것이다.
하지만 #define prt(x) printf("asdf" #x) 로 할 경우 x위치에 들어오는 값을 string 형식으로 바꿔주기 때문에 가능하다.
거기에 ##을 붙일 경우 원래 있던 token 에 맞춰서 붙여준다.
예를 들어 #define prt(x) printf("%d", a##x) 로 정의할 경우
int a1=1; prt(1) 을 할 경우 printf("%d", a1)으로 바꿔준다.
특수한 경우 굉장히 많이 쓰이기 때문에 알아두면 굉장히 유용하다.
1-4. #ifdef #ifndef #else #endif #if #elif
선행처리자에 의해 수행되는 조건문이다.
#ifdef DEBUG 처럼 선행처리자의 시점에서 이미 정의된 것인지 아닌지에 따라서 컴파일할 때 문장을 남겨두나 가려두나의 차이가 있다. 여기서 DEBUG라는 임시 이름은 숫자나 값이 배정되어 있지 않아도 되며 단순히 #define DEBUG 까지만 선언해도 define 됐다고 가정하여 #ifdef 가 수행된다.
#ifndef 는 #ifdef 와 반대로 정의가 되었으면 실행하는게 아닌, 정의가 되지 않았으면 실행되는 것이다.
#else 는 #ifdef나 #ifndef가 false일 경우 수행할 문장들을 뜻하며 #endif는 #ifdef 든 #ifndef 든 끝에 항상 '무조건' 쓰여야 한다.
#if 와 #elif 의 경우 조건문을 쓸 수 있다.
#define DEBUG 만 했을 경우 #if DEBUG 는 수행될지 안될지 정확하게 파악할 수 없다.
#ifdef 와 다르게 참, 거짓을 판별하기 때문에 ==, !=, <=, >=, <, >, &&, ||, ! 연산이 사용 가능하다.
하지만 선행처리기 수준에서 처리되는 조건문이기 때문에 컴파일시 생성되는 변수들은 사용하면 어떻게 작동될지 예측하기 힘들기 때문에 #define 에서 처리되는 값이거나 상수(정수)만을 사용해야한다.
1-5. #error
우리가 흔히 코드를 짜다 보면 맞닥뜨리는 에러같은 형식을 만들 수 있다.
가령 #ifndef DEBUG #error error! 라고 만들어두면 DEBUG 가 정의되지 않았을 시 선행처리기 수준에서 error 메세지가 나온다.
1-6. #pragma
표준적인 기능들이 아닌, 사용자가 사용하는 compiler 가 제공하는 선행 처리 기능들을 사용하고 싶을 때 사용한다.
이는 compiler에 의존하므로 다른 compiler 와 호환되지 않을 수도 있기 때문에 잘 확인하고 써야 한다.
'C언어' 카테고리의 다른 글
C언어의 기초 - 연결 리스트 Linked List (0) | 2021.03.04 |
---|---|
C) 간단한 정렬 - 버블 정렬(Bubble sort), 삽입 정렬(Insertion Sort), 빠른 정렬(Quick sort) (0) | 2021.02.23 |
C언어의 기초 - 비트 연산자 (0) | 2021.02.06 |
C언어의 기초 - 포인터 (0) | 2021.02.06 |
C언어의 기초 - 구조체와 공용체 (0) | 2021.02.03 |