[26 매크로와 선행처리기]
// [26-1]
// 1) 편집->선행처리->컴파일->링크->실행 단계를 자세히 설명하시오.
// .c, .obj, .exe 확장자가 보이도록 캡쳐
// 먼저 편집은 사람이 프로그래밍 언어로 코드를 작성하는 단계이다. 그 후 전처리기가 선행처리 지시자들을 처리한 후
// 컴파일 과정이 진행된다. 컴파일은 c언어 코드를 이진 목적 파일로 변환하는 단계이다. 이후 링크 과정에서 프로그램에 필요한
// 여러 목적 파일과 기본 라이브러리를 cpu가 처리할 수 있는 하나의 실행 파일로 결합하는 단계이다. 이후 실행 파일에 있는
// 코드가 메인 메모리에 로드되어 순차적으로 실행된다.
// 2) c언어 표준 라이브러리 함수의 선언, 호출, 정의는 어디에 있고 어떤 형태로 존재하는지 설명하시오.
// 함수의 선언 : c언어에서 기본 제공되는 헤더 파일 <stdio.h>, <stdlib.h>, <string.h> 등 에 c언어 코드의 형태로 존재한다.
// 함수의 호출 : 사용자의 소스 코드 파일 내에서 적절한 인자를 전달하여 이루어진다.
// 함수의 정의 : 표준 라이브러리 파일(msvcrt.lib, msvcrt.dll)에 이진 코드의 형태로 저장되어 있고
// 이때 링크 과정에서 사용되는 msvcrt.lib 는 c프로그래밍 ide에 실행 시 사용되는 msvcrt.dll는 운영체제 내부에 저장되어 있다.
// 3) 매크로 상수의 필요성을 설명하고 예제를 찾아 실행 후 설명하시오.
// 매크로 상수는 프로그램에서 사용되는 고정된 값을 의미 있는 이름으로 정의하여 코드의 가독성, 유지보수성, 안정성을 높이기 위해 사용된다.
// 예제는 아래 4번으로 대체
// 4) 아래 코드를 매크로상수를 이용하여 가독성을 높이시오.
// 현재방향 이라는 상태에 따른 값을 문자열 매크로로 정의하여 값이 아닌 특정 의미를 가진 문자열로 대체하여
// 가독성을 높이고 상태에 대한 값이 바뀔 경우 매크로 정의문만 수정하면 되어 유지보수성이 높아졌다.
#include <stdio.h>
#define TOP 10
#define BOTTOM 20
#define LEFT 30
#define RIGHT 40
int main(void) {
int direction = TOP;
switch (direction) {
case TOP: printf("현재방향 : 위"); break;
case BOTTOM: printf("현재방향 : 아래"); break;
case LEFT: printf("현재방향 : 왼쪽"); break;
case RIGHT: printf("현재방향 : 오른쪽"); break;
}
return 0;
}
// [26-2]
// 1) 매크로 함수의 필요성을 설명하고 예제를 찾아 실행 후 코드를 설멍하시오([26-1] - 3번 예제와 동일).
// 2) 헤더파일의 중복 포함문제의 발생 이유와 방지법을 설명하시오.
// 특정 파일에 헤더파일을 include 하고 다른 파일(예 func.c)을 include 할 때 func.c에 동일한 헤더 파일이
// include될 경우 중복으로 포함되어 헤더파일 내의 구조체 정의나 전역변수 선언이 중복되어 오류가 발생한다.
// 이를 방지하기 위해 ifdef~#endif 로 헤더 파일의코드를 감싼 후 첫 문장에 특정 문자열 매크로를 정의하여
// 앞에 정의가 추가될 경우 뒤의 정의는 컴파일하지 않게 하여 중복 문제를 해결한다.
// 3) #if~#elif~#endif 지시자를 사용하는 구체적인 예제를 찾아 실행 및 설명하시오.
// 사용하는 운영체제에 따라 컴파일러가 자동정의하는 메크로를 사용하여 운영체제에 따라 파일주소표현을 변경하여
// 파일의 내용을 복사하는 운영체제 독립적인 코드이다.
#include <stdio.h>
#if defined(_WIN32)
#define PATH_SEPARATOR '\\'
#elif defined(__linux__)
#define PATH_SEPARATOR '/'
#else
#define PATH_SEPARATOR '/'
#endif
int main(void)
{
FILE* fin = fopen("input.txt", "r");
FILE* fout = fopen("output.txt", "w");
int ch;
if (fin == NULL || fout == NULL) {
printf("파일 열기 실패\n");
return 1;
}
while ((ch = fgetc(fin)) != EOF) {
fputc(ch, fout);
}
fclose(fin);
fclose(fout);
printf("파일 복사 완료 (경로 구분자: %c)\n", PATH_SEPARATOR);
return 0;
}
// 4) #ifdef~#endif 지시자를 사용하는 구체적인 예제를 찾아 실행 및 설명하시오.
// 각 led에 특정 값을 주어 출력값에 따라 led의 상태를 출력하는 코드를 DEBUG 매크로가 정의되었을 떄만 출력하도록 구현했음
#include <stdio.h>
#include <stdint.h>
#define DEBUG
uint8_t LED_PORT = 0x0; // 4비트 LED 상태 저장
void set_led(uint8_t state) {
LED_PORT = state;
#ifdef DEBUG
printf("LED 상태: 0x%X\n", LED_PORT);
#endif
}
int main(void) {
for (int i = 0; i < 4; i++) set_led(1 << i);
for (int i = 0; i < 4; i++) set_led(0x0);
return 0;
}
[27 파일의 분할과 헤더파일의 디자인]
// [27-1]
// 1) 두 개의 정수를 키보드로부터 입력받아 큰수를 구해 출력하는 분할 파일 프로그램을 작성하시오.
// main.c = 변수 선언 및 함수호출, mylib.c = 함수 정의, mylib.h = 함수 선언 및 선행처리문
// main.c
#include "mylib.h"
int main(void) {
int x, y;
get_data(&x, &y);
big(x, y);
return 0;
}
// mylib.h
#ifndef MYLIB_H
#define MYLIB_H
void big(int a, int b);
void get_data(int* x, int* y);
#endif
// mylib.c
#define _CRT_SECURE_NO_WARNINGS
#pragma warning(disable:6031)
#include <stdio.h>
#include "mylib.h"
void big(int a, int b) {
printf("최대값은: %d", (a > b) ? a : b);
}
void get_data(int* x, int* y) {
printf("정수를 2개 입력하시오:");
scanf("%d %d", x, y);
}
// 2) 1개의 정수를 입력 받아 아래 함수값을 출력하는 분할 파일 프로그램을 작성하시오.
// y = 1.5(x*x*x) - 3x + 0.7
// main.c
#include "mylib.h"
int main(void) {
int x;
get_data(&x);
get_y(x);
return 0;
}
// mylib.h
#ifndef MYLIB_H
#define MYLIB_H
void get_y(int x);
void get_data(int* x);
#endif
// mylib.c
#define _CRT_SECURE_NO_WARNINGS
#pragma warning(disable:6031)
#include <stdio.h>
#include "mylib.h"
void get_y(int x) {
printf("y = %lf", (1.5 * (x * x * x)) - (3*x) + 0.7);
}
void get_data(int* x) {
printf("정수를 1개 입력하시오:");
scanf("%d", x);
}
// 3) 이름, 점수를 멤버로 가지는 구조체에 데이터를 입력받아 이름이 먼저 나오는 사람을 출력하는 분할 파일 프로그램을 작성하시오.
// main.c
#include "mylib.h"
int main(void) {
Student stus[3];
get_data(stus, 3);
firststu(stus, 3);
return 0;
}
// mylib.h
#ifndef MYLIB_H
#define MYLIB_H
typedef struct {
char name[10];
int score;
}Student;
void firststu(Student* stus, int len);
void get_data(Student* stus, int len);
#endif
// mylib.c
#define _CRT_SECURE_NO_WARNINGS
#pragma warning(disable:6031)
#include <stdio.h>
#include <string.h>
#include "mylib.h"
void firststu(Student* stus, int len) {
int first = 0;
for (int i = 1; i < len; i++) {
first = strcmp(stus[first].name, stus[i].name) > 0 ? i : first;
}
printf("제일앞에 나오는 이름은 %s, 점수는 %d점", stus[first].name, stus[first].score);
}
void get_data(Student* stus, int len) {
for (int i = 0; i < len; i++) {
printf("이름을 입력하시오:");
scanf("%s", stus[i].name);
printf("성적을 입력하시오:");
scanf("%d", &stus[i].score);
}
}
// [26, 27장 정리문제]
// 1) 선행처리과정에 대하여 설명하시오.
// 선행처리 과정은 컴파일 전에 수행되는 단계로, 소스 코드에 작성된 선행처리지시자를 컴파일러가 읽을 c언어 코드로 변환하는 과정이다.
// 2) 선행처리지시자의 역할을 설명하시오.
// 선행처리지시자는 주로 헤더 파일 포함, 매크로 정의, 조건부 컴파일과 같은 컴파일러의 동작을 제어하며
// 이를 통해 소스 코드를 쉽게 원하는 형태로 가공할 수 있다.
// 3) 매크로 상수의 필요성을 설명하시오.
// 매크로 상수는 특정 상수를 의미있는 이름으로 정의하여 코드의 가독성과 유지보수성을 높인다.
// 4) pragma 지시자의 용도를 설명하시오.
// pragma 지시자는 컴파일러를 제어하는 지시자로, 컴파일러의 기본 동작 규칙을 상황에 맞게 조정하기 위해 사용하는 전처리기 지시자이다.
// 따라서 컴파일러에 따라 달리지기 때문에 이식성이 떨어질 수 있다.
// 5) 분할 컴파일의 의미를 설명하시오.
// 분할컴파일은 프로그램을 특정 기준에 따라 여러 개의 파일로 나누어 각각 컴파일하는 것을 말한다.
// 6) 분할 컬파일을 사용하는 이유를 설명하시오.
// 프로그램 내의 코드들을 용도나 특성별로 분류하여 구조를 알기쉽게 만들고, 재사용과 유지보수가 용이해진다.
// 또한 수정된 파일만 재컴파일하여 디버깅에 도움이 된다.
// 7) 링크 과정에 대하여 설명하시오.
// 링크 과정은 분할 컴파일로 생성된 여러 목적 파일과 라이브러리를 결합하여 하나의 실행 파일로 만드는 과정이다.
// 8) extern 키워드의 사용 방법과 시점을 설명하시오.
// extern 키워드는 다른 소스 파일에 정의된 전역 변수를 참조하기 위해서 사용하고 함수에는 기본적으로 적용된다.
// 9) static전역 변수의 용도를 설명하시오.
// static전역 변수는 해당 소스 파일 내에서만 접근 가능하게 하는 것으로 수명과 관계없이 접근 범위를 제한하고 싶을 때 사용한다.
// 10) 헤더 파일의 중복포함을 방지하는 방법에 대하여 설명하시오.
// #ifndef와 #endif 사이에 특정 문자열 매크로를 정의하여 중복될 경우 컴파일되지 않도록 한다.
// 11) 코드를 헤더파일과 소스파일로 나누는 기준을 설명하시오.
// 먼저 헤더파일에는 외부에 공개되어도 괜찮거나, 함수의 선언, 구조체 또는 열거형의 정의와 매크로나 상수, extern으로 선언된 전역변수,
// 즉 다른 파일의 컴파일 과정에서 필요한 모든 정보들을 작성하고, 소스 파일에는 구현부와 함수 또는 변수의 정의, 내부 전용 함수 및 변수가 작성된다.