다양한 형태로 입출력 하는 함수
a.txt에는
이름1 70 80 90
이름2 40 50 60
이름3 100 20 30
이런식으로 텍스트가 들어있다.
a.txt 에서 이름(문자열)과 세 개의 점수(정수)를 순서대로 읽어온 뒤 세 점수의 총점과 평균을 계산한 후 b.txt 파일에 이름, 총점, 평균을 정해진 형식으로 새로 쓴다.
1. 서식 지정 입출력: fprintf - 각 데이터 사이에 있는 공백/줄바꿈은 자동 무시, fscanf - 데이터를 지정된 형태로 변환하여 출력
2. 줄/문자열 단위 입출력: fgets - \n을 문자열 끝에 포함하여 읽고 공백문자를 구분자로 사용하지 않고 오직 줄 끝(\n) 또는 파일의 끝(EOF) 까지 읽는다, fputs - 주어진 문자열을 파일 포인터가 가리키는 파일에 출력한
#include <stdio.h>
int main(void) {
// FILE *ifp, *ofp;
FILE *ifp;
FILE *ofp;
char name[20];
int kor, eng, math;
int total;
double avg;
int res;
// ifp = fopen("~/work/basic3/a.txt", "r"); // 상대경로
// ifp = fopen("./a.txt", "r");
ifp = fopen("a.txt", "r");
if (ifp == NULL) {
printf("Read Error\\n");
return 1;
}
ofp = fopen("b.txt", "w");
if(ofp == NULL) {
printf("Write Error");
return 1;
}
while (1) {
// 1. a.txt 내용을 읽어오기
res = fscanf(ifp, "%s%d%d%d", name, &kor, &eng, &math);
if (res == EOF) // EOF는 -1
break;
// 2. 총점 구하기
total = kor + eng + math;
avg = total / 3.0; // 3.0을 쓰므로 캐스팅 연산자 안써도 됨
// 3. b.txt에 각 학생의 총점과 평균값을 파일에 출력
fprintf(ofp, "%s%5d%7.lf\\n", name, total, avg);
}
// 4. 리소스 반환
fclose(ifp);
fclose(ofp);
return 0;
}
파일 입출력 함수 비교: 서식 vs. 문자열
| 함수 | 목적 | 처리 단위 | 서식 지정 | 주로 사용되는 상황 |
| fscanf | 파일에서 읽기 | 서식(Format)에 맞는 개별 데이터 항목 | O (%d, %s, %f 등) | 구조화된 숫자 및 문자열 데이터를 읽을 때 (예: CSV 파일, 점수 기록) |
| fprintf | 파일에 쓰기 | 서식(Format)에 맞춘 출력 문자열 | O (%d, %s, %f 등) | 데이터를 일정한 형식으로 파일에 기록할 때 (예: 보고서, 포맷된 로그) |
| fgets | 파일에서 읽기 | 한 줄 전체 (줄바꿈 문자 포함) | X (오직 문자열) | 파일의 내용을 한 줄씩 읽어와 처리할 때 (예: 텍스트 파일 전체 복사, 줄 단위 파싱) |
| fputs | 파일에 쓰기 | 문자열 | X (오직 문자열) | 가공되지 않은 문자열 데이터를 파일에 기록할 때 (예: 줄바꿈 없이 단순 텍스트 쓰기) |
마찬가지로 a2.txt 에서 글자 읽은 후, 총합과 평균 구해서 b2.txt에 쓰는 코드이다
#include <stdio.h>
int main(void) {
char name[20];
int kor, eng, math;
int total = 0;
double avg = 0;
int res;
FILE *ifp, *ofp;
ifp = fopen("a2.txt", "r");
if (ifp == NULL) {
printf("Read Error");
return 1;
}
ofp = fopen("b2.txt", "w");
if (ifp == NULL) {
printf("Write Error");
return 1;
}
while (1) {
res = fscanf(ifp, "%s%d%d%d", name, &kor, &eng, &math);
if (res == EOF) {
break;
}
total = kor + eng + math;
avg = (double) total / 3;
fprintf(ofp, "%s %d %lf\\n", name, total, avg);
}
fclose(ifp);
fclose(ofp);
return 0;
}
스트림 파일 입출력 시 버퍼 공유 문제
a3.txt 파일에
25
Tom
이라는 내용이 있을 경우
fscanf(fp, "%d", &age) 실행 시 fscanf는 파일에서 정수(25)를 성공적으로 읽어 age에 저장한다. 하지만 fscanf는 숫자 뒤에 남아있는 줄 바꿈 문자는 읽지 않고 입력 버퍼에 남겨둔다.
fgetc(fp) != '\n' 또는 fgets(name, sizeof(name), fp); 실행 시 스트림에 남아있는 줄바꿈 문자를 첫 번째 문자로 인식하고 바로 문자열의 끝으로 간주하여 읽기를 종료한다.
결과적으로 fgets는 a3.txt 파일의 두번째 줄 Tom을 읽지 못하고 name 변수에는 줄바꿈 문자만 포함되거나 첫 번째 줄의 잔여물만 남게된다.
따라서 while(fgetc(fp) != '\n');는 이 문제를 회피하는 일반적인 방법이다
#include <stdio.h>
int main(void) {
FILE* fp;
int age;
char name[20]; // char* name
fp = fopen("a3.txt", "r");
if (fp == NULL) {
printf("Read Error");
return 1;
}
fscanf(fp, "%d", &age);
while(fgetc(fp) != '\\n'); // 버퍼 공유 문제 처리 팁
fgets(name, sizeof(name), fp);
printf("나이: %d, 이름: %s", age, name);
fclose(fp);
return 0;
}
fread와 fwrite: 변환 없는 메모리 입출력
fread와 fwrite는 C 라이브러리의 바이너리 입출력 함수이다.
fwrite: 메모리에 저장된 변수(num = 10이 저장된 4바이트)의 비트 패턴을 그대로 파일에 복사한다. 문자열이나 서식으로 변환하는 과정이 없다.
fread: 파일에 저장된 바이트들을 읽어와 메모리 변수 위치에 그대로 복사한다.
fwrite가 생성한 b4.txt 파일의 내용을 xxd -b b4.txt 로 확인하면 4바이트가 저장되어 있으며, 이 4바이트의 순서가 바로 엔디안을 나타낸다
리틀 엔디안: 인텔 x86, amd, arm 계열의 대다수 cpu가 사용한다. 가장 낮은 바이트가 가장 낮은 메모리 주소에 저장된다. (10의 16진수는 0x0000000A이고, b4.txt에 저장되는 순서는 0A 00 00 00)
빅 엔디안: 네트워크 프로토콜(tcp/ip, 모토로라, ibm 메인프레임, 초기 맥 컴퓨터의 poserPC cpu 등에서 사용되었다. 데이터의 가장 높은 바이트가 가장 낮은 메모리 주소에 저장된다 (정수 10의 16진수는 0x0000000A 이다. 저장되는 순서 00 00 00 0A)
#include <stdio.h>
// <https://modoocode.com/68>
int main(void) {
FILE *afp, *bfp; // 파일포인터
int num = 10;
int res;
afp = fopen("a4.txt", "wt"); // text 모드로 write
fprintf(afp, "%d", num);
fclose(afp);
bfp = fopen("b4.txt", "wb"); // binary 모드로 write
fwrite(&num, sizeof(num), 1, bfp);
fclose(bfp);
bfp = fopen("b4.txt", "rb"); // binary 모드로 read
fread(&res, sizeof(res), 1, bfp); // b4.txt binary 파일을 읽어옴
printf("%d\\n", res); // 10 정수로 출력
// size_t: long unsigned int
fclose(bfp);
return 0;
}

사용자 정의 헤더파일
중복 포함을 방지하기 위해 #pragma once 또는 #ifndef, #define 전처리 지시자를 사용한다
#ifndef, #define, #endif 를 인클루드 가드라고 부른다
#pragma once 지시자도 같은 역할을 하지만 이는 컴파일러 종속적일 수 있다.
student.h
#ifndef MY_H
#define MY_H // 이 두줄은 pragma once 랑 같음
//#pragma once
typedef struct {
int num;
char name[20];
} Student;
#endif
main.c
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include "student.h" // 사용자 정의 헤더
int main() {
Student a = { 315, "홍길동" };
printf("학번: %d, 이름: %s\\n", a.num, a.name);
return 0;
}
#define 전처리기 지시자 이해하기
#define은 c/c++ 언어에서 전처리 단계에 사용되는 지시자인데 컴파일이 시작되기 전에 소스코드 내에서 정의된 매크로를 찾아 그 내용으로 문자열 치환을 수행한다. 이런 특성 때문에 빠르다는 장점이 있지만, 타입 검사를 할 수 없어 디버깅이 어렵다.
#define 은 const 처럼 사용할 수 있지만 차이점은 다음과 같다
| 특징 | #define (전처리 상수) | const (타입이 있는 상수) |
| 처리 시점 | 전처리 단계 (컴파일 이전) | 컴파일 단계 (일반 변수와 동일) |
| 메모리 사용 | 대개 메모리를 사용하지 않음 (단순 치환) | 메모리를 사용하거나 레지스터에 저장될 수 있음 |
| 타입 검사 | 없음 (문자열 치환) | 있음 (자료형 검사) |
| 디버깅 | 어려움 (치환된 코드가 에러를 발생시킬 수 있음) | 쉬움 (컴파일러가 오류를 잡아줌) |
| 예시 | #define PI 3.141592 | const double PI = 3.141592; |
매크로 함수
#define을 함수처럼 인수를 받아 처리하도록 만들 수 있고, 이를 매크로 함수라고 한다. 일반 함수와 달리 함수 호출 오버헤드 없이 코드를 인라인 하는 효과를 나타낸다. 모든 인수를 괄호로 묶지 않으면, 잘못된 결과가 나올 수 있다 (#define MUL(a, b) a*b 처럼 사용하면 안된다는 뜻)
매크로 함수는 함수 호출이 아니라 코드 치환이기 때문에 함수를 호출할 때 발생하는 스택 푸시/팝 등의 오버헤드가 전혀 없어 매우 빠르게 동작한
| 매크로 | 정의 | 설명 |
| SUM | #define SUM(a, b) ((a) + (b)) | a와 b를 안전하게 더하기. |
| MUL | #define MUL(a, b) ((a) * (b)) | a와 b를 안전하게 곱하기. |
이미 정의된 매크로
컴파일러에 의해 자동으로 값이 설정되며 디버깅이나 로그 기록에 유용하게 사용된다
#line 지시자: 컴파일러에게 다음 코드 줄부터는 지정된 행 번호와 파일 이름을 사용하라고 지시한다. 따라서 이 지시자 이후에 __FILE__과 __LINE__의 값이 변경되어 적용된다.
#include <stdio.h>
void func(void);
int main() {
printf("컴파일 날짜와 시간 : %s, %s\\n\\n", __DATE__, __TIME__); // __DATE__: 컴파일을 시작한 날짜, __TIME__: 컴파일을 시작한 시간
printf("파일명: %s\\n", __FILE__); // 전체 디렉터리 경로를 포함한 파일명
printf("함수명: %s\\n", __FUNCTION__); // 매크로명이 사용된 함수 이름
printf("행번호: %d\\n", __LINE__); // 매크로명이 사용된 행 번호
#line 100 "macro.c"
func();
return 0;
}
void func() {
printf("\\n\\n");
printf("파일명: %s\\n", __FILE__); // macro.c (조작된 파일명)
printf("함수명: %s\\n", __FUNCTION__); // func
printf("행번호: %d\\n", __LINE__); // 100번 이후의 행번호 (조작된 행번호)
}
| 매크로 | 타입 | 설명 | 출력 예시 |
| __DATE__ | 문자열 (char*) | 소스 파일이 컴파일된 날짜 (Mmm dd yyyy 형식) | "Nov 17 2025" |
| __TIME__ | 문자열 (char*) | 소스 파일이 컴파일된 시간 (hh:mm:ss 형식) | "14:30:55" |
| __FILE__ | 문자열 (char*) | 현재 파일의 전체 경로 및 파일 이름 | "/home/user/Ex19-4.c" |
| __FUNCTION__ | 문자열 (char*) | 매크로가 사용된 현재 함수 이름 | "main" 또는 "func" |
| __LINE__ | 정수 (int) | 매크로가 사용된 소스 코드의 행 번호 | 12 |
| __STDC__ | 정수 (int) | 컴파일러가 표준 C를 준수하면 1로 정의됨 | 1 |
문자열화 연산자와 토큰연결 연산자
##: 변수 이름이나 함수 이름을 동적으로 생성할 때
#: 디버깅이나 로깅 목적으로 변수 이름이나 표현식을 출력할
전처리 과정
NAME_CAT(a, 1) = 10; > (a ## 1) = 10; > a1 = 10;
NAME_CAT(a, 2) = 20; > (a ## 2) = 20; > a2 = 20;
PRINT_EXPR(a1+a2); > printf("a1 + a2" " = %d\n", a1 + a2);
PRINT_EXPR(a2 - a1); > printf("a2 - a1" " = %d\n", a2 - a1);
#include <stdio.h>
#define PRINT_EXPR(x) printf(#x " = %d\\n", x);
#define NAME_CAT(x, y) (x ## y)
int main() {
int a1, a2;
NAME_CAT(a, 1) = 10;
NAME_CAT(a, 2) = 20;
PRINT_EXPR(a1 + a2);
PRINT_EXPR(a2 - a1);
return 0;
}
분할 컴파일 아키텍쳐
분할 컴파일을 사용하여 전역변수를 모듈간에 공유하는 방법
point.h
//#pragma once
// header파일 하는 역할: 함수 정의, 구조체 정의
#ifndef _POINT_H_
#define _POINT_H_
// void myprint(); // 함수헤더
typedef struct {
int x;
int y;
} Point;
extern Point Gpoint;
#endif
point.c
#include "point.h"
void myprint() {
// 함수의 구현부가 들어간다
}
Point Gpoint = { 0, 0 };
main.c
#include <stdio.h>
#include "point.h"
int main() {
printf("첫 좌표 Gpoint = (%d, %d)\\n", Gpoint.x, Gpoint.y);
Gpoint.x = 10;
Gpoint.y = 20;
printf("변환 좌표 Gpoint = (%d, %d)\\n", Gpoint.x, Gpoint.y);
return 0;
}
wsl
power shell
bash
uname
uname -a
lsb_realase -a
exit
wsl --list --online
wsl
cd ~/
pwd
ls
ls -a
history
man history
clear
history
!402 # 이렇게 하면 라인에 해당하는 명령어 실행됨
!402:p # 라인에 해당하는 명령어가 뜸
sudo apt install vim
vi hello.c # :set nu 하면 줄이 뜸
ls # 해당 디렉터리에 있는 파일 목록 나열
cd # 디렉터리 이동
pwd # 현재 경로 출력
rm # 파일 또는 디렉터리 삭제 (-rf)
cp # 파일 또는 디렉터리 복사
touch # 크기가 0인 새 파일을 생성, 이미 존재하는 경우 수정 시간 변경
mv # 파일 또는 디렉터리 이름 변경 또는 위치 이동
mkdir # 새로운 디렉터리 생성
cd ~/work
mkdir cmdTest1
ls
cd ~/work/cmdTest1/
touch abc.txt
ls -l
rm abc.txt
echo “Hello World!” > hello.txt # 리눅스 자체가 c 프로그램
cat hello.txt
date
echo “Hello World” > abc.txt
cat abc.txt # 웹에서 restful. 24시간 365일 서버가 켜져있다.
env
env | grep LANG # grep을 사용하면 원하는 단어(LANG)가 포함되는 결과만 찾을 수 있다
env | grep LANG > abc.txt
cat abc.txt
rmdir # 디렉터리 삭제
cat # 텍스트로 작성된 파일을 화면에 출력
head # 텍스트로 작성된 파일의 앞 10행 출력
tail # 텍스트로 작성된 파일의 마지막 10행 출력
more # 텍스트로 작성된 파일을 화면에 페이지 단위로 출력
less # more과 비슷
file # 어떤 종류의 파일인지
cd /var/log
cat dpkg.log
head dpkg.log
head -3 dpkg.log # 위에서 3줄
tail -5 dpkg.log # 밑에서 5줄
오라클 리눅스 설치: 오라클 리눅스는 클라우드 서버, 분산 컴퓨팅에 많이 사용된다. 또 리더 역할을 하는 서버가 죽었을 때 다른 서버가 자동으로 역할을 이어받는 클러스터링 및 HA 시스템은 분산 컴퓨팅의 핵심이다. 오라클 리눅스는 Unbreakable Enterprise Kernel을 제공한다.
wsl --list --online
wsl --install -d OracleLinux_9_5
sudo dnf update
sudo dnf groupinstall "Development Tools" # 강력한 툴체인. 우분투에서는 sudo apt install build-essential
gcc
g++
make
sudo dnf install nano
nano hello.c
gcc -o hello hello.c
./hello
exit
wsl --list --verbose
'c' 카테고리의 다른 글
| c 언어 문제 (0) | 2025.11.14 |
|---|---|
| 파일처리 (0) | 2025.11.13 |
| 구조체 (0) | 2025.11.12 |
| 배열, 포인터와 구조 (0) | 2025.11.11 |
| 이중포인터 N차원 배열 (1) | 2025.11.10 |