안녕하세요! 이번 글은 이전 글과 달리 내용이 조금 많은데요. 따로 하나씩 정리하기에는 보시는 분들이 시간이 오래 걸릴 것 같아서 이렇게 구성해보았습니다. 여러분의 시간은 소중하니까요!
C언어 메모리 동적 할당의 개념과 함께 예시 및 사용자 정의 자료형인 구조체 활용까지 그리고 파일 입출력, 전처리, 분할 컴파일 내용을 정리해보았습니다.
다뤄야 할 부분이 많아서 조금은 내용이 많을 수 있으나 한 번쯤 읽고 넘어가시면 굉장히 유익한 내용이 될거라 자신합니다.
1. 메모리 동적 할당
동적 할당 함수
- 동적 메모리 할당은 프로그램 실행 중에 메모리를 필요할 때 할당하고, 사용이 끝나면 해제하는 것을 말한다.
- malloc 함수는 지정된 크기의 메모리를 힙 영역에 할당하며, 성공하면 해당 메모리의 시작 주소를 반환한다.
- 할당된 메모리를 다 사용하면 free 함수를 사용하여 반드시 해제해야 한다.
#include <stdlib.h>
int main() {
int *p = (int *)malloc(sizeof(int) * 5); // 정수 5개에 해당하는 메모리 할당
if (p == NULL) {
printf("메모리 할당 실패\n");
return 1;
}
for (int i = 0; i < 5; i++) {
p[i] = i + 1;
printf("p[%d] = %d\n", i, p[i]);
}
free(p); // 동적 메모리 해제
return 0;
}
- malloc을 통해 할당받은 메모리는 배열처럼 인덱스를 통해 접근할 수 있다.
- 위 예시에서 p [i]와 같은 방식으로 메모리를 배열처럼 활용할 수 있다.
- 동적 메모리를 할당하는 함수로는 calloc과 realloc도 있다.
- calloc은 할당된 메모리를 초기화하며, realloc은 이미 할당된 메모리의 크기를 조절한다.
int *arr = (int *)calloc(5, sizeof(int)); // 정수 5개 크기의 메모리 할당 및 0으로 초기화
arr = (int *)realloc(arr, sizeof(int) * 10); // 메모리 크기 재조정
free(arr);
동적 할당 저장 공간의 활용
- 동적 할당을 사용하여 문자열을 저장할 수 있다.
- 문자열의 길이가 실행 중에 결정되는 경우 malloc을 사용하여 메모리를 할당하고 문자열을 저장할 수 있다.
char *str = (char *)malloc(20 * sizeof(char)); // 길이 20의 문자열 저장 공간 할당
strcpy(str, "Hello, World!");
printf("문자열: %s\n", str);
free(str); // 동적 메모리 해제
- 동적 할당된 문자열을 함수로 전달하여 처리할 수 있다.
void printString(char *s) {
printf("문자열: %s\n", s);
}
int main() {
char *str = (char *)malloc(50 * sizeof(char));
strcpy(str, "동적 할당된 문자열");
printString(str);
free(str);
return 0;
}
- 명령행 인수를 사용하는 방법으로 프로그램 실행 시 외부에서 입력된 값을 받아 동적으로 메모리를 활용할 수 있다.
int main(int argc, char *argv[]) {
if (argc < 2) {
printf("사용법: %s <문자열>\n", argv[0]);
return 1;
}
printf("입력된 인수: %s\n", argv[1]);
return 0;
}
2. 사용자 정의 자료형
구조체
- 구조체는 여러 개의 데이터를 하나의 그룹으로 묶어서 관리할 수 있는 사용자 정의 자료형이다.
- 구조체는 struct 키워드를 사용하여 선언한다.
typedef struct {
char name[20];
int age;
float height;
} Person;
int main() {
Person person = {"Alice", 25, 170.5};
printf("이름: %s, 나이: %d, 키: %.1f\n", person.name, person.age, person.height);
return 0;
}
- 구조체 멤버로 배열, 포인터, 다른 구조체 등을 포함할 수 있다.
- 이를 통해 복잡한 데이터를 간단히 표현할 수 있다.
typedef struct {
char name[30];
int scores[5];
float *grades;
} Student;
- 구조체 변수를 선언하면서 초기화할 수 있으며, 구조체 변수 간의 대입 연산도 가능하다.
Person p1 = {"Bob", 30, 180.0};
Person p2;
p2 = p1; // p2에 p1의 값 대입
- 구조체 변수는 함수의 매개변수로 전달할 수 있으며, 주소를 전달하여 효율성을 높일 수 있다.
void printPerson(Person *p) {
printf("이름: %s, 나이: %d, 키: %.1f\n", p->name, p->age, p->height);
}
int main() {
Person person = {"Charlie", 22, 175.0};
printPerson(&person);
return 0;
}
구조체 활용, 공용체, 열거형
- 구조체 포인터를 사용하면 -> 연산자를 통해 구조체 멤버에 접근할 수 있다.
Person person = {"Dave", 28, 165.0};
Person *p = &person;
printf("이름: %s, 나이: %d\n", p->name, p->age);
- 구조체 배열을 사용하면 여러 개의 구조체를 효율적으로 관리할 수 있다.
Person people[3] = {
{"Eve", 20, 160.0},
{"Frank", 27, 175.5},
{"Grace", 22, 168.0}
};
for (int i = 0; i < 3; i++) {
printf("이름: %s, 나이: %d\n", people[i].name, people[i].age);
}
- 구조체 배열을 함수로 전달하여 처리할 수 있다.
void printPeople(Person people[], int size) {
for (int i = 0; i < size; i++) {
printf("이름: %s, 나이: %d, 키: %.1f\n", people[i].name, people[i].age, people[i].height);
}
}
int main() {
Person people[2] = { {"Hank", 24, 172.0}, {"Ivy", 29, 158.5} };
printPeople(people, 2);
return 0;
}
- 자기 참조 구조체는 구조체 안에 자기 자신을 가리키는 포인터를 포함할 수 있다.
- 이를 통해 연결 리스트 같은 자료구조를 구현할 수 있다.
typedef struct Node {
int data;
struct Node *next;
} Node;
- 공용체는 union 키워드를 사용하며, 여러 멤버를 공유하여 하나의 메모리 공간을 절약하는 데 유용하다.
typedef union {
int i;
float f;
char str[20];
} Data;
int main() {
Data data;
data.i = 10;
printf("정수: %d\n", data.i);
data.f = 220.5;
printf("실수: %.2f\n", data.f);
strcpy(data.str, "Hello");
printf("문자열: %s\n", data.str);
return 0;
}
- 열거형은 enum 키워드를 사용하여 관련된 상수들의 집합을 정의한다.
typedef enum {
RED,
GREEN,
BLUE
} Color;
int main() {
Color color = GREEN;
printf("색상: %d\n", color); // 출력: 1
return 0;
}
- typedef를 사용하면 기존 자료형에 새로운 이름을 정의할 수 있다.
typedef unsigned long ulong;
ulong num = 1000;
printf("num: %lu\n", num);
3. 파일 입출력
파일 개방과 입출력
- 파일을 사용하기 위해서는 먼저 fopen 함수를 사용하여 파일을 개방하고, 사용 후에는 fclose 함수를 사용해 파일을 닫아야 한다.
FILE *fp = fopen("data.txt", "r");
if (fp == NULL) {
printf("파일 열기 실패\n");
return 1;
}
// 파일 작업 수행
fclose(fp);
- 파일 입출력은 파일 포인터를 사용하며, 스트림을 통해 데이터를 읽고 쓸 수 있다.
- 파일 포인터는 FILE 형식이며, 파일을 읽고 쓰는 데 중요한 역할을 한다.
- fgetc 함수는 파일에서 한 문자를 읽어온다.
- 파일이 끝에 도달하면 EOF를 반환한다.
char ch;
while ((ch = fgetc(fp)) != EOF) {
printf("%c", ch);
}
- fputc 함수는 파일에 한 문자를 쓴다.
FILE *fp = fopen("output.txt", "w");
if (fp != NULL) {
fputc('A', fp);
fclose(fp);
}
- C 언어에서는 프로그램이 시작될 때 자동으로 세 가지 표준 스트림 파일이 개방된다:
- 1. stdin (표준 입력)
- 2. stdout (표준 출력)
- 3. stderr (표준 오류)
- 이들은 각각 사용자로부터 데이터를 입력받거나 출력하기 위해 사용된다.
- 파일은 텍스트 파일과 바이너리 파일로 구분되며, 파일을 개방할 때 모드를 설정하여 구분할 수 있다.
- 텍스트 파일은 사람이 읽을 수 있는 형식이고, 바이너리 파일은 컴퓨터가 이해하는 형식으로 저장된다.
FILE *fp1 = fopen("textfile.txt", "r"); // 텍스트 파일 개방
FILE *fp2 = fopen("binaryfile.bin", "rb"); // 바이너리 파일 개방
- + 모드: r+, w+, a+ 등과 같이 사용하여 읽기와 쓰기를 모두 가능하게 한다.
- fseek: 파일 내의 특정 위치로 이동할 때 사용한다.
fseek(fp, 0, SEEK_SET); // 파일의 처음으로 이동
- rewind: 파일 포인터를 파일의 처음으로 이동시킨다.
rewind(fp);
- feof: 파일의 끝에 도달했는지 확인할 때 사용한다.
if (feof(fp)) {
printf("파일의 끝에 도달했습니다.\n");
}
다양한 파일 입출력 함수
- fgets 함수는 파일에서 한 줄씩 읽어올 때 사용한다.
- fputs 함수는 문자열을 파일에 쓸 때 사용된다.
FILE *fp = fopen("data.txt", "r");
char buffer[100];
if (fp != NULL) {
while (fgets(buffer, sizeof(buffer), fp) != NULL) {
printf("%s", buffer);
}
fclose(fp);
}
FILE *fp_out = fopen("output.txt", "w");
if (fp_out != NULL) {
fputs("Hello, World!\n", fp_out);
fclose(fp_out);
}
- fscanf와 fprintf는 파일에서 다양한 형태의 데이터를 읽고 쓸 수 있게 해 준다.
FILE *fp = fopen("data.txt", "r");
int num;
char str[20];
if (fp != NULL) {
fscanf(fp, "%d %s", &num, str);
printf("읽은 값: %d, %s\n", num, str);
fclose(fp);
}
FILE *fp_out = fopen("output.txt", "w");
if (fp_out != NULL) {
fprintf(fp_out, "정수: %d, 문자열: %s\n", 100, "Hello");
fclose(fp_out);
}
- 파일의 출력 버퍼는 즉시 반영되지 않고 버퍼에 저장되었다가 나중에 쓰일 수 있다.
- 이를 해결하기 위해 fflush 함수를 사용하여 버퍼에 있는 데이터를 즉시 파일에 반영할 수 있다.
FILE *fp = fopen("output.txt", "w");
if (fp != NULL) {
fputs("버퍼에 저장된 데이터\n", fp);
fflush(fp); // 버퍼를 비우고 파일에 반영
fclose(fp);
}
- fread와 fwrite 함수는 주로 바이너리 파일을 처리할 때 사용되며, 특정 크기만큼 데이터를 읽거나 쓴다.
FILE *fp = fopen("binaryfile.bin", "wb");
int data[5] = {1, 2, 3, 4, 5};
if (fp != NULL) {
fwrite(data, sizeof(int), 5, fp); // 데이터 배열을 파일에 씀
fclose(fp);
}
fp = fopen("binaryfile.bin", "rb");
int read_data[5];
if (fp != NULL) {
fread(read_data, sizeof(int), 5, fp); // 파일에서 데이터를 읽어옴
for (int i = 0; i < 5; i++) {
printf("%d ", read_data[i]);
}
fclose(fp);
}
4. 전처리와 분할 컴파일
전처리 지시자
- #include 지시자는 외부 파일을 포함할 때 사용한다.
- 주로 표준 라이브러리나 사용자 정의 헤더 파일을 포함하기 위해 사용된다.
#include <stdio.h>
#include "myheader.h"
- #define을 사용하여 상수나 코드를 매크로로 정의할 수 있다.
#define PI 3.14159
#define SQUARE(x) ((x) * (x))
- 매크로 함수는 간단한 연산을 정의할 때 유용하며, 인라인 치환으로 실행 속도를 높일 수 있다.
- C 언어에서는 이미 정의된 여러 매크로가 있으며, 예를 들어 __FILE__, __LINE__ 등이 있다.
printf("파일 이름: %s, 라인 번호: %d\n", __FILE__, __LINE__);
- # 연산자는 매크로의 인수를 문자열로 변환한다.
- ## 연산자는 매크로 인수를 결합한다.
#define STRINGIFY(x) #x
#define CONCAT(a, b) a##b
- 조건부 컴파일은 특정 조건에 따라 코드의 일부를 컴파일할지 결정하는 기능이다.
#ifdef DEBUG
printf("디버그 모드\n");
#endif
분할 컴파일
- 프로그램을 여러 파일로 나누어 관리하고 컴파일하는 것을 분할 컴파일이라고 한다.
- 이를 통해 코드의 재사용성과 관리 효율성을 높일 수 있다.
- extern: 전역 변수를 다른 파일에서 사용할 수 있도록 선언할 때 사용된다.
- static: 파일 내에서만 접근 가능한 변수를 정의할 때 사용한다.
// a.c 파일
int globalVar = 10;
// b.c 파일
extern int globalVar;
- 헤더 파일은 함수 선언과 매크로 정의를 포함하여 여러 파일에서 공통으로 사용할 수 있다.
- 중복 포함 문제를 해결하기 위해 #ifndef, #define, #endif 가드가 사용된다.
#ifndef MYHEADER_H
#define MYHEADER_H
void myFunction();
#endif
혹시 이해가 어려우시다면, 이전 글을 먼저 참고해보시면 좋을 것 같습니다.
C 언어 함수, 배열, 포인터 사용법과 예제
안녕하세요! 오늘은 C언어의 핵심이자 꼭 알아야 할 내용인 함수, 배열, 포인터를 정리해보았습니다. 무엇보다 배열과 포인터는 C언어에서 몰라서는 안될 핵심과도 같기 때문에 반드시 이해하고
actshiny.com
C 언어 배열, 포인터, 문자열 활용 심화
안녕하세요! 지난 글에서 C언어 함수, 배열, 포인터에 대해 간단하게 다뤄보았습니다. 혹시나 해당 내용에 대해 기초적인 부분을 모르시는 분들은 바로 이전 글을 보고 오시면 좋을 것 같습니다.
actshiny.com
C 언어 변수, 다차원 배열, 응용 포인터 정리
안녕하세요! 지난 글들에서 C언어의 배열, 포인터 내용을 학습하셨을텐데요. 이어서 변수의 영역과 함수의 데이터 공유 내용과 더불어 다차원 배열, 포인터 배열, 응용 포인터에 대해 정리해보
actshiny.com
반응형
'IT & AI > AI 지식' 카테고리의 다른 글
JAVA 조건문, 반복문, 참조 타입(배열) 정리 (2) | 2024.11.21 |
---|---|
JAVA 변수, 연산자, 데이터 입출력 (1) | 2024.11.20 |
C 언어 변수, 다차원 배열, 응용 포인터 정리 (1) | 2024.11.19 |
C 언어 배열, 포인터, 문자열 활용 심화 (0) | 2024.11.19 |
C 언어 함수, 배열, 포인터 사용법과 예제 (0) | 2024.11.18 |