C 언어의 제어문은 프로그램의 흐름을 제어하며, 주요 제어문으로 조건문반복문, 흐름을 조정하는 breakcontinue가 있습니다.

1. 조건문

1-1. if문

  • 용도: 조건에 따라 코드 실행.
  • 형식:
     
if (조건) { ... } else { ... }

1-2. switch문

  • 용도: 여러 값에 따라 실행 분기.
  • 형식:
     
switch (표현식) {
    case 값1: ... break;
    default: ...
}

2. 반복문

2-1. for문

  • 특징: 초기값, 조건, 증감 모두 포함.
  • 형식:
     
for (초기값; 조건; 증감) { ... }

2-2. while문

  • 특징: 조건이 참일 때만 실행.
  • 형식:
     
while (조건) { ... }

2-3. do-while문

  • 특징: 최소 한 번 실행 후 조건 검사.
  • 형식:
     
do { ... } while (조건);

3. break와 continue

3-1. break

  • 특징: 반복문이나 switch문을 즉시 종료.
  • 형식:
     
if (조건) break;

3-2. continue

  • 특징: 반복문의 현재 반복 건너뛰기.
  • 형식:
     
if (조건) continue;

예제

짝수 출력 (break, continue 사용):

#include <stdio.h>

int main() {
    for (int i = 1; i <= 10; i++) {
        if (i % 2 != 0) continue; // 홀수 건너뜀
        if (i > 6) break;         // 6 초과 시 종료
        printf("%d\n", i);
    }
    return 0;
}

 

조건문과 반복문은 프로그램의 기본 흐름을 관리하고, break와 continue로 유연하게 제어할 수 있습니다.

'C' 카테고리의 다른 글

C) 연산자  (0) 2024.11.25
C) 변수와 자료형  (0) 2024.11.23
C) 개발 환경 설정: 컴파일러 설치 및 설정  (1) 2024.11.22
C) C 언어 소개  (0) 2024.11.22
C 언어로 OOP 스타일의 Stack 구현하기  (0) 2024.11.22

C 언어에서 연산자는 데이터 조작과 논리를 구현하기 위한 핵심 도구입니다. 산술, 비교, 논리, 비트 연산자에 대해 살펴보겠습니다.

1. 산술 연산자 (Arithmetic Operators)

수학적 계산을 수행합니다.

+ 덧셈 a + b a와 b의 합
- 뺄셈 a - b a에서 b를 뺌
* 곱셈 a * b a와 b의 곱
/ 나눗셈 a / b a를 b로 나눔
% 나머지 연산 a % b a를 b로 나눈 나머지

2. 비교 연산자 (Relational Operators)

두 값 간의 관계를 비교하고, 결과는 참(1) 또는 거짓(0)입니다.

== 같음 a == b 참: a와 b가 같음
!= 같지 않음 a != b 참: a와 b가 다름
> a > b 참: a가 b보다 큼
< 작음 a < b 참: a가 b보다 작음
>= 크거나 같음 a >= b 참: a가 b 이상
<= 작거나 같음 a <= b 참: a가 b 이하

3. 논리 연산자 (Logical Operators)

논리값(참 또는 거짓)을 결합하거나 부정합니다.

&& 논리 AND a && b 참: 둘 다 참일 때만 참
|| 논리 OR a || b 참: 하나라도 참일때
! 논리 NOT !a 참: a가 거짓일 때 참

4. 비트 연산자 (Bitwise Operators)

값을 비트 단위로 연산합니다.

& 비트 AND a & b a와 b의 대응 비트가 모두 1이면 1
| 비트 OR a | b a와 b의 비트중 하나라도 1이면 1
^ 비트 XOR a ^ b a와 b의 대응 비트가 다를 때 1
~ 비트 NOT ~a a의 비트를 반전
<< 왼쪽 시프트 a << 2 a를 왼쪽으로 2비트 이동
>> 오른쪽 시프트 a >> 2 a를 오른쪽으로 2비트 이동

 

연산자우선순위 및 결합 방향

C 언어의 연산자 우선순위는 코드의 실행 순서를 결정하며, 동일한 우선순위를 가진 연산자는 결합 방향에 따라 처리됩니다. 아래는 주요 연산자의 우선순위를 정리한 표입니다.

1 () [] -> . 괄호, 배열, 구조체 접근 왼쪽 → 오른쪽
2 ++ -- + - ! ~ 단항 연산자 (전위, 부정, 비트 반전 등) 오른쪽 → 왼쪽
3 * / % 곱셈, 나눗셈, 나머지 왼쪽 → 오른쪽
4 + - 덧셈, 뺄셈 왼쪽 → 오른쪽
5 << >> 비트 시프트 왼쪽 → 오른쪽
6 < <= > >= 관계 연산자 왼쪽 → 오른쪽
7 == != 같음, 다름 왼쪽 → 오른쪽
8 & 비트 AND 왼쪽 → 오른쪽
9 ^ 비트 XOR 왼쪽 → 오른쪽
10 | 비트 OR 왼쪽 → 오른쪽
11 && 논리 AND 왼쪽 → 오른쪽
12 || 논리 OR 왼쪽 → 오른쪽
13 ?: 삼항 연산자 오른쪽 → 왼쪽
14 = += -= *= /= %= 대입 및 복합 대입 연산자 오른쪽 → 왼쪽
15 , 쉼표 연산자 왼쪽 → 오른쪽

 

연산자는 코드의 논리와 기능을 구현하는 핵심 도구입니다. 다양한 연산자를 이해하고 적재적소에 활용하면 효율적이고 간결한 코드를 작성할 수 있습니다.

'C' 카테고리의 다른 글

C) 제어문: 조건문, 반복문  (0) 2024.11.28
C) 변수와 자료형  (0) 2024.11.23
C) 개발 환경 설정: 컴파일러 설치 및 설정  (1) 2024.11.22
C) C 언어 소개  (0) 2024.11.22
C 언어로 OOP 스타일의 Stack 구현하기  (0) 2024.11.22

프로그래밍에서 변수는 데이터를 저장하는 상자이며, 자료형은 이 상자가 어떤 종류의 데이터를 담을지 정의합니다. 자료형은 시스템 아키텍처(32비트, 64비트)와 운영체제(Linux, Windows)에 따라 크기와 범위가 다를 수 있습니다. 또한, **오버플로우(Overflow)**와 **언더플로우(Underflow)**와 같은 특수 상황도 이해해야 합니다.

1. 자료형의 정의와 변수 선언

  • 변수: 데이터를 저장하기 위한 이름이 있는 공간.
  • 자료형: 저장할 데이터의 유형(숫자, 문자 등)과 크기를 정의.

자료형 예제

int age = 25;        // 정수형
float pi = 3.14;     // 실수형
char grade = 'A';    // 문자형
 

2. 기본 자료형과 운영체제 및 아키텍처별 차이

자료형 32bit Linux 64bit Linux 32bit Windows 64bit Windows 범위(signed) 범위(unsigned)
char 1 byte 1 byte 1 byte 1byte -128 ~ 127 0 ~ 255
short 2 byte 2 byte 2 byte 2 byte -32,768 ~ 32,767 0 ~ 65,535
int 4 byte 4 byte 4 byte 4 byte -2,147,483,648 ~ 2,147,483,647 0 ~ 4,294,967,295
long 4 byte 8 byte 4 byte 4 byte -2,147,483,648 ~ 2,147,483,647 (32비트 기준) 0 ~ 4,294,967,295
long long 8 byte 8 byte 8 byte 8 byte -9,223,372,036,854,775,808 ~ ... 0 ~ 18,446,744,073,709,551,615
float 4 byte 4 byte 4 byte 4 byte 1.2E-38 ~ 3.4E+38 N/A

double 8 byte 8 byte 8 byte 8 byte 2.3E-308 ~ 1.7E+308
long double 12~16  byte 12~16  byte 8 byte 8 byte 환경에 따라 다름

운영체제와 아키텍처 차이

  1. Linux vs Windows:
    • Linux: long은 64비트에서 8바이트, Windows에서는 4바이트.
    • long double은 Linux에서 더 높은 정밀도(12~16바이트)를 가짐.
  2. 32비트 vs 64비트:
    • 64비트 환경에서는 주소 크기가 증가하므로 일부 자료형(long)이 더 큰 크기를 가질 수 있음.

3. signed와 unsigned의 차이

  • signed: 양수와 음수를 모두 표현.
  • unsigned: 음수를 제외하고 양수 범위를 2배로 확장.

예제 코드

signed int s = -10;    // 양수와 음수 모두 가능
unsigned int u = 300;  // 음수 불가능, 양수 범위 확장

4. 오버플로우와 언더플로우

오버플로우(Overflow)

변수에 저장 가능한 최대값을 초과했을 때 다시 최소값으로 돌아가는 현상.
예: unsigned char에서 255를 초과하면 0으로 돌아감.

언더플로우(Underflow)

변수에 저장 가능한 최소값 아래로 내려갔을 때 최대값으로 돌아가는 현상.
예: unsigned char에서 0 아래로 가면 255로 돌아감.

예제 코드

#include <stdio.h>

int main() {
    unsigned char max = 255;
    unsigned char min = 0;

    max += 1;  // 255를 넘어서 0으로 돌아감
    min -= 1;  // 0 아래로 가서 255로 돌아감

    printf("Overflow: %u\n", max);
    printf("Underflow: %u\n", min);

    return 0;
}

 

5. 자료형 안전하게 사용하기

  1. 최대값과 최소값 확인
    <limits.h>를 사용하여 자료형의 최대값과 최소값을 확인할 수 있습니다.
  2. 초기화 습관
    선언된 변수는 항상 초기화하세요. 초기화하지 않으면 쓰레기 값(Random Value)을 가질 수 있습니다.
#include <stdio.h>
#include <limits.h>

int main() {
    printf("Max int: %d, Min int: %d\n", INT_MAX, INT_MIN);
    printf("Max unsigned int: %u\n", UINT_MAX);
    return 0;
}

 

6. 실습 코드: 자료형과 오버플로우 테스트

#include <stdio.h>
#include <limits.h>

int main() {
    unsigned int u_max = UINT_MAX; // 최대값
    int s_max = INT_MAX;           // 최대값
    int s_min = INT_MIN;           // 최소값

    printf("Unsigned int max: %u\n", u_max);
    printf("Signed int max: %d\n", s_max);
    printf("Signed int min: %d\n", s_min);

    // 오버플로우와 언더플로우 테스트
    u_max += 1; // 최대값 초과
    s_max += 1; // 최대값 초과
    s_min -= 1; // 최소값 초과

    printf("After Overflow: Unsigned: %u, Signed max: %d, Signed min: %d\n", u_max, s_max, s_min);

    return 0;
}

 

 

자료형은 프로그래밍의 기초이자, 안전한 코딩의 핵심입니다.
운영체제와 아키텍처에 따라 자료형의 크기와 동작이 달라질 수 있으므로 항상 이를 염두에 두고 작성해야 합니다. 또한, 오버플로우언더플로우 같은 특수 상황을 이해하면 버그를 예방할 수 있습니다.

'C' 카테고리의 다른 글

C) 제어문: 조건문, 반복문  (0) 2024.11.28
C) 연산자  (0) 2024.11.25
C) 개발 환경 설정: 컴파일러 설치 및 설정  (1) 2024.11.22
C) C 언어 소개  (0) 2024.11.22
C 언어로 OOP 스타일의 Stack 구현하기  (0) 2024.11.22

안녕하세요! 오늘은 GitGitHub를 사용해 코드를 관리하고 협업하는 방법을 알아보겠습니다.
Git은 분산 버전 관리 도구로, 코드를 안전하게 관리하고 변경 이력을 추적할 수 있습니다. GitHub는 이러한 Git 리포지토리를 클라우드에서 호스팅하는 서비스입니다.

1. Git 설치 및 초기 설정

1단계: Git 설치

  1. Git 공식 사이트에서 운영 체제에 맞는 설치 파일을 다운로드합니다.
  2. 설치 중 다음 옵션을 주의 깊게 선택합니다:
    • Default Editor: 기본 텍스트 편집기를 선택 (예: VS Code).
    • Adjust PATH environment: PATH에 Git 추가를 선택합니다.

2단계: 초기 설정

설치가 완료되면 터미널(또는 Git Bash)을 열고 아래 명령어를 입력합니다:

git config --global user.name "Your Name"
git config --global user.email "your_email@example.com"
  • user.name: 커밋 기록에 표시될 이름
  • user.email: 커밋 기록에 포함될 이메일 주소

 

2. Git 기본 명령어

Git의 핵심은 **로컬 리포지토리(Repository)**를 생성하고, 변경 사항을 저장하며, 원격 리포지토리와 동기화하는 것입니다.

1단계: 리포지토리 생성

git init
  • 현재 디렉토리를 Git 리포지토리로 초기화합니다.

2단계: 파일 추가 및 커밋

git add <파일명>          # 특정 파일 추가
git add .                # 모든 변경 사항 추가
git commit -m "커밋 메시지"
  • add: 변경된 파일을 스테이징 영역에 추가
  • commit: 변경 사항을 로컬 리포지토리에 저장

3단계: 상태 확인 및 로그 보기

git status               # 현재 상태 확인
git log                  # 커밋 기록 보기

 

 

3. GitHub와 연동하기

GitHub를 사용하면 로컬에서 작업한 내용을 원격 리포지토리에 백업하거나 공유할 수 있습니다.

1단계: GitHub 계정 생성

  1. GitHub 공식 사이트에서 계정을 생성합니다.
  2. 새 리포지토리를 생성합니다:
    • 리포지토리 이름 지정 (예: my-project).
    • README.md 초기화 선택 가능.

2단계: 로컬 리포지토리를 GitHub에 연결

GitHub 리포지토리 생성 후, URL을 복사한 다음 아래 명령어를 실행합니다:

git remote add origin https://github.com/username/my-project.git
git branch -M main
git push -u origin main
  • remote add origin: 로컬 리포지토리를 GitHub와 연결
  • push: 변경 사항을 원격 리포지토리에 업로드

 

4. 협업 및 브랜치 사용

Git과 GitHub의 강점 중 하나는 협업입니다. 팀 프로젝트에서 각자 작업을 분리하기 위해 브랜치를 활용합니다.

1단계: 브랜치 생성 및 전환

git branch feature-branch        # 새 브랜치 생성
git checkout feature-branch      # 생성한 브랜치로 전환

2단계: 병합(Merge)

작업이 완료되면 브랜치를 병합하여 메인 브랜치에 통합합니다:

git checkout main                # 메인 브랜치로 전환
git merge feature-branch         # 브랜치 병합

 

5. 자주 사용하는 명령어 요약

명령어설명

git init 새로운 Git 리포지토리 초기화
git clone <URL> 원격 리포지토리 복제
git add <파일명> 파일을 스테이징 영역에 추가
git commit -m "메시지" 변경 사항을 커밋
git push 원격 리포지토리에 업로드
git pull 원격 리포지토리에서 변경 사항 가져오기
git branch 브랜치 목록 확인
git checkout <브랜치명> 브랜치 전환

 

 

Git과 GitHub는 현대 개발에서 필수적인 도구입니다. 위의 과정을 따라 초기 설정과 기본 명령어를 익히셨다면, 실습을 통해 더 깊이 이해할 수 있습니다.

이번 포스팅에서는 64비트 MinGW를 설치하고 Visual Studio Code(VS Code)에서 코딩하는 방법을 알아보겠습니다.
64비트 컴파일러는 더 큰 메모리 공간과 향상된 성능을 제공하므로 최신 시스템에서 사용을 권장합니다.

1. MinGW-w64 소개

MinGW-w64란?

MinGW-w64는 MinGW의 확장판으로, 64비트와 32비트 환경 모두를 지원합니다.

  • GCC 기반으로 다양한 플랫폼에서 동작하며, 특히 64비트 Windows 개발에 최적화되어 있습니다.

2. MinGW-w64 설치

1단계: 다운로드

  1. MinGW-w64 공식 다운로드 페이지로 이동합니다.
  2. 64-bit GCC 버전(예: GCC x.x.x x86_64-posix-seh)을 선택하여 설치 파일을 다운로드합니다.
    • SEH(Synchronous Exception Handling): 64비트 Windows에 적합.
    • Posix 스레드 모델 사용 추천.

2단계: 설치

  1. 다운로드한 설치 파일을 실행하고, 설치 경로를 지정합니다.
    • 예: C:\mingw64
  2. 설치가 완료되면, bin 디렉토리를 환경 변수(Path)에 추가합니다.
    • 예: C:\mingw64\bin

3단계: 설치 확인

  1. 명령 프롬프트를 열고, 아래 명령어를 입력합니다: 
  2. gcc --version
  3. 설치된 GCC 버전 정보가 출력되면 정상적으로 설정된 것입니다.

3. Visual Studio Code와 연동

1단계: VS Code 설치 및 확장팩 추가

  1. VS Code 공식 홈페이지에서 다운로드 후 설치합니다.
  2. C/C++ 확장팩을 설치합니다.
    • VS Code 왼쪽의 확장 탭(Ctrl+Shift+X)에서 C/C++을 검색하고 설치.

64비트 MinGW-w64와 VS Code를 사용하면 최신 시스템의 성능을 최대한 활용할 수 있습니다.
개발 환경을 성공적으로 설정했다면, 간단한 프로그램을 작성해 실습을 시작해 보세요!

'C' 카테고리의 다른 글

C) 연산자  (0) 2024.11.25
C) 변수와 자료형  (0) 2024.11.23
C) C 언어 소개  (0) 2024.11.22
C 언어로 OOP 스타일의 Stack 구현하기  (0) 2024.11.22
C) C언어로 회원가입/로그인 시스템 구현하기!  (1) 2023.03.19

안녕하세요! 오늘은 프로그래밍 언어의 역사에서 빠질 수 없는 C 언어에 대해 알아보겠습니다. C 언어는 현대 프로그래밍의 기초를 닦은 중요한 언어로, 그 역사와 특징, 그리고 다양한 사용 용도까지 함께 살펴보겠습니다.

C 언어의 역사

C 언어는 1972년, 벨 연구소의 데니스 리치(Dennis Ritchie)에 의해 개발되었습니다.
당시에는 UNIX 운영체제를 효율적으로 개발하기 위해 만들어졌는데, 이 과정에서 C 언어는 이전의 B 언어와 어셈블리 언어의 단점을 극복하며 큰 주목을 받았습니다.

  • 1978년: 데니스 리치와 브라이언 커니핸(Brian Kernighan)이 저술한 "The C Programming Language"가 출판되면서 C 언어는 대중화되기 시작했습니다.
  • 1989년: ANSI(미국 표준 협회)에서 ANSI C 표준을 제정하여 언어의 일관성을 강화했습니다. 이후 ISO 표준으로도 채택되었습니다.
  • 현대: 오늘날에도 C 언어는 다양한 시스템과 소프트웨어의 핵심 언어로 자리 잡고 있습니다.

C 언어의 특징

C 언어는 다음과 같은 강력한 특징을 지니고 있습니다:

1. 저수준 언어와 고수준 언어의 조화

C 언어는 어셈블리 수준의 저수준 언어와, 고수준 언어의 가독성과 추상화된 개념을 동시에 제공합니다.
이를 통해 하드웨어 제어효율적인 프로그래밍이 모두 가능해졌습니다.

2. 이식성

C 언어로 작성된 프로그램은 다양한 플랫폼에서 쉽게 실행할 수 있습니다.
이는 C 언어가 시스템 간의 독립성을 제공하여, 유닉스, 윈도우, 리눅스 등 다양한 환경에서 사용되도록 했습니다.

3. 풍부한 라이브러리

C 표준 라이브러리는 기본적인 데이터 처리, 문자열, 파일 입출력 등 많은 기능을 제공합니다.

4. 구조적 프로그래밍

C 언어는 함수 중심의 구조적 프로그래밍을 지원합니다. 이를 통해 프로그램을 모듈화하고 유지보수성을 높이는 데 큰 도움이 됩니다.

5. 속도와 효율성

C 언어는 하드웨어에 밀접하게 접근할 수 있어 빠른 실행 속도를 자랑합니다.
그래서 운영체제, 임베디드 시스템 등 성능이 중요한 분야에서 주로 사용됩니다.

C 언어의 사용 용도

C 언어는 아래와 같은 다양한 분야에서 활용되고 있습니다:

1. 운영체제 개발

C 언어는 UNIX와 같은 운영체제를 개발하기 위해 만들어졌습니다.
현재도 Linux 커널이나 Windows 운영체제의 많은 부분이 C 언어로 작성되어 있습니다.

2. 임베디드 시스템

소형 기기(마이크로컨트롤러, IoT 기기 등)와 같은 임베디드 시스템의 소프트웨어를 작성하는 데 C 언어가 많이 사용됩니다.

3. 컴파일러 및 인터프리터

다른 프로그래밍 언어의 컴파일러와 인터프리터를 만드는 데에도 사용됩니다. 예를 들어, Python의 일부 컴포넌트는 C로 구현되었습니다.

4. 게임 개발

그래픽 처리와 하드웨어 제어가 중요한 게임 개발에서도 C는 주요 언어로 사용됩니다.

5. 과학 및 공학 분야

수학적 계산이나 시뮬레이션 같은 고성능 연산이 필요한 분야에서도 C 언어가 널리 사용됩니다.

 

C 언어는 단순하지만 강력한 도구로서, 컴퓨터 과학의 핵심을 이해하는 데 필수적인 언어입니다.
C 언어의 탄생 배경과 강력한 특징을 알게 되셨다면, 이제 직접 코드를 작성하며 그 매력을 경험해 보시는 건 어떨까요?

앞으로 이 블로그에서 C 언어의 기본부터 심화 주제까지 다뤄보겠습니다.

C 언어로 OOP 스타일의 Stack 구현하기

최근 C 언어에서 객체지향 프로그래밍(OOP)을 구현하려고 시도하면서, C++에서 많이 사용되는 벡터(Vector)를 구현하기 전에 먼저 스택(Stack)을 구현해보기로 했습니다. 자료구조를 공부하면서 Stack의 push 연산부터 구현해보기로 했고, 이를 OOP 스타일로 어떻게 구현할 수 있는지 살펴보았습니다.

이번에 구현한 스택은 C 언어에서 객체지향 프로그래밍의 개념을 적용하여, 스택에 데이터를 push, pop, clear, delete하는 메서드를 갖는 구조체를 작성하는 방식입니다.

프로젝트 링크

이 프로젝트는 GitHub - Object-Oriented-C-Language에서 확인할 수 있습니다. 이곳의 algorithm.h와 algorithm.c 파일을 참고하여 스택을 구현했습니다.

algorithm.h (헤더 파일)

#include "main.h"

#ifndef __ALGORITHM_H
#define __ALGORITHM_H

#pragma pack(push, 1)
typedef struct STACK {
    void** data;               // 스택에 저장될 데이터
    size_t top;                // 스택의 탑(가장 최근에 저장된 데이터)
    size_t size;               // 스택이 차지하는 메모리 크기
    size_t byteSize;           // 각 데이터 항목의 크기
    void (*push)(struct STACK*, void*);  // push 함수 포인터
    void* (*pop)(struct STACK*);         // pop 함수 포인터
    void (*clear)(struct STACK*);        // clear 함수 포인터
    void (*delete)(struct STACK*);       // delete 함수 포인터
} Stack;
#pragma pack(pop)

Stack new_stack(size_t type);  // 새로운 스택 생성 함수

#define new_Stack(type) new_stack(sizeof(type))  // 매크로를 사용하여 타입에 맞는 스택 생성
#endif

이 헤더 파일에서는 Stack 구조체를 정의하고, 스택을 조작하는 메서드들을 함수 포인터로 연결했습니다. 각 메서드는 스택에 데이터를 추가하거나 제거하고, 스택을 초기화하거나 삭제하는 기능을 합니다. 또한, new_stack() 함수는 새로운 스택을 생성하는 함수로, 각 데이터 항목의 크기를 인자로 받습니다.

algorithm.c (구현 파일)

#include "algorithm.h"

// stackPush: 스택에 데이터를 추가하는 함수
// stack: 스택 객체, data: 스택에 넣을 데이터
static void stackPush(Stack* stack, void* data) {
    // 스택에 저장된 데이터의 크기를 확장하기 위해 realloc 호출
    // 현재 스택의 크기보다 하나 더 큰 크기로 재할당
    stack->data = (void**)realloc(stack->data, (stack->top + 1) * sizeof(void*));
    
    // 새 공간에 데이터를 할당하고, 해당 위치에 data 복사
    *(stack->data + stack->top) = malloc(stack->byteSize);  // 새 데이터를 위한 메모리 할당
    memcpy(*(stack->data + stack->top), data, stack->byteSize);  // 실제 데이터 복사

    // 스택의 top을 증가시키고, 스택의 크기를 다시 계산
    stack->top++;
    stack->size = stack->top * sizeof(void*);
}

// stackPop: 스택에서 데이터를 꺼내는 함수
// stack: 스택 객체
static void* stackPop(Stack* stack) {
    // 스택의 맨 위에서 데이터를 제거하고, 그 데이터를 반환
    void* removedData = *(stack->data + stack->top - 1);

    // top을 하나 줄이고, 스택 크기 재계산
    stack->top--;
    stack->size = stack->top * sizeof(void*);
    
    // 스택 메모리를 재할당하여 크기 줄이기
    stack->data = (void**)realloc(stack->data, stack->size);
    
    // 꺼낸 데이터 반환
    return removedData;
}

// stackClear: 스택에 저장된 모든 데이터를 해제하고, 스택을 비우는 함수
// stack: 스택 객체
static void stackClear(Stack* stack) {
    // 스택에 있는 각 데이터를 해제
    for (size_t i = 0; i < stack->top; i++) {
        free(*(stack->data + i));  // 각 데이터 메모리 해제
    }
    
    // 스택 정보를 초기화
    stack->top = 0;   // 스택의 top을 0으로 초기화
    stack->size = 0;   // 스택의 크기도 0으로 초기화
    free(stack->data); // 스택 데이터 메모리 해제
    stack->data = NULL;  // 데이터 포인터 초기화
}

// stackDelete: 스택을 삭제하는 함수
// stack: 스택 객체
static void stackDelete(Stack* stack) {
    // 스택을 비운 후
    stackClear(stack);
    
    // 스택 관련 정보를 초기화
    stack->byteSize = 0;  // 각 데이터 항목의 크기 초기화
    stack->data = NULL;    // 데이터 포인터 초기화
}

// new_stack: 새로운 스택을 생성하는 함수
// type: 스택에 저장될 데이터 타입의 크기
Stack new_stack(size_t type) {
    Stack stack;
    stack.data = NULL;  // 초기 데이터 포인터는 NULL
    stack.push = stackPush;   // push 함수 포인터 연결
    stack.pop = stackPop;     // pop 함수 포인터 연결
    stack.clear = stackClear; // clear 함수 포인터 연결
    stack.delete = stackDelete; // delete 함수 포인터 연결
    stack.top = 0;  // 초기 top 값은 0
    stack.size = 0; // 초기 크기는 0
    stack.byteSize = type;  // 데이터 항목 크기 설정 (type)
    
    return stack;  // 초기화된 스택 객체 반환
}

각 함수 설명:

  1. stackPush:
    • 스택에 데이터를 추가하는 함수입니다.
    • realloc을 사용해 기존의 data 배열을 확장하고, 새 데이터를 할당하여 복사합니다.
  2. stackPop:
    • 스택에서 데이터를 제거하는 함수입니다.
    • top을 감소시키고, 스택의 크기를 다시 계산하여 realloc을 사용해 메모리를 조정합니다.
  3. stackClear:
    • 스택에 저장된 모든 데이터를 해제하고 스택을 초기화합니다.
  4. stackDelete:
    • stackClear를 호출하여 스택을 비운 후, 스택 구조체 내의 추가적인 메모리 해제를 수행합니다.
  5. new_stack:
    • 새로운 스택을 생성하는 함수입니다. 타입 크기를 인자로 받아 스택 구조체를 초기화하고 반환합니다.

매크로 new_Stack:

매크로 new_Stack(type)은 new_stack(sizeof(type))을 호출하는 방식으로, 스택을 쉽게 생성할 수 있게 도와줍니다. 예를 들어, new_Stack(int)를 사용하면 int 타입에 맞는 스택을 생성할 수 있습니다.

예시 사용법:

int main() {
    Stack intStack = new_Stack(int);  // int 타입의 스택 생성
    int value = 10;

    intStack.push(&intStack, &value);  // 값 10을 스택에 push
    int* poppedValue = (int*)intStack.pop(&intStack);  // 값을 pop하고 출력

    printf("Popped value: %d\n", *poppedValue);  // 출력: Popped value: 10

    intStack.delete(&intStack);  // 스택 삭제
    return 0;
}

결론

C 언어에서 객체지향적인 방식으로 스택을 구현하면, 코드의 재사용성과 유지보수성이 높아지며, 다양한 데이터 타입을 처리할 수 있는 유연성을 갖게 됩니다. void* 포인터를 활용해 어떤 데이터 타입도 저장할 수 있도록 구현한 것이 큰 특징입니다. 이를 통해 C 언어에서도 객체지향 프로그래밍의 핵심 개념을 적용할 수 있다는 점에서 유용한 학습이 되었습니다.

얼마 전 친구들이 디코에서 회원가입/로그인 시스템을 만들고 있길래 재밌어 보여서 C로 만들어 봤다.

https://github.com/sunwookim05/Member-management-system

 

GitHub - sunwookim05/Member-management-system

Contribute to sunwookim05/Member-management-system development by creating an account on GitHub.

github.com

먼저 main.h 를 만든다 모든 코드의 베이스가 됄 코드이다.

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <time.h>

#pragma once
#ifndef __MAIN_H
#define __MAIN_H

#define import extern
#define final const
#define null NULL
#define false 0
#define true 1

typedef char *String;
typedef char byte;
typedef char int8_t;
typedef short int16_t;
typedef int int32_t;
typedef long long int64_t;
typedef unsigned char ubyte;
typedef unsigned char uint8_t;
typedef unsigned short uint16_t;
typedef unsigned int uint32_t;
typedef unsigned long long uint64_t;
typedef uint8_t boolean;

#pragma warning(pop)
#endif

그리고 전에 만들었던 Scanner-with-C 를 활용하고 싶어 Scanner와 System 클래스를 구현했다.

 

System.h

#include "main.h"

#pragma once
#ifndef __SYSTEM_H
#define __SYSTEM_H

struct __stdin_t;

#pragma pack(push, 1)
/**
 * The {@code System} class contains several useful class fields
 * and methods. It cannot be instantiated.
 *
 * Among the facilities provided by the {@code System} class
 * are standard input, standard output, and error output streams;
 * access to externally defined properties and environment
 * variables; a means of loading files and libraries; and a utility
 * method for quickly copying a portion of an array.
 *
 * @since   1.0
 */
typedef struct _System{
    struct __stdout_t{
        /**
        * @brief System out print function
        * @param format
        * @return void
        */
        void (*print)(const String, ...);
        /** 
        * @brief System out println function
        * @param format
        * @return void
        */
        void (*println)(const String, ...);
    }out;
    struct __stdin_t{
        int (*read)();
    } in;
}SYSTEM;
#pragma pack(pop)

#pragma warning(pop)
#endif

Scanner.h

#include "main.h"

#pragma once
#ifndef __Scanner_H
#define __Scanner_H

struct __stdin_t;

#pragma pack(push, 1)
/** 
 * @brief Scanner class
 */
typedef struct Scanner{
    /**
    * @brief User input char
    * @param void
    * @return char
    */    
    char (*nextChar)(void);

    /**
     * @brief User input Byte
     * @param void
     * @return byte
     */
    byte (*nextByte)(void);

    /**
     * @brief User input Short
     * @param void
     * @return int16_t
     */
    int16_t (*nextShort)(void);

    /**
     * @brief User input Int
     * @param void
     * @return int32_t
     */
    int32_t (*nextInt)(void);

    /**
     * @brief User input Long
     * @param void
     * @return int64_t
     */
    int64_t (*nextLong)(void);

    /**
     * @brief User input Unsigned Byte
     * @param void
     * @return ubyte
     */
    ubyte (*nextUByte)(void);

    /**
     * @brief User input Unsigned Short
     * @param void
     * @return uint16_t
     */
    uint16_t (*nextUShort)(void);

    /**
     * @brief User input Unsigned Int
     * @param void
     * @return uint32_t
     */
    uint32_t (*nextUInt)(void);

    /**
     * @brief User input Unsigned Long
     * @param void
     * @return uint64_t
     */
    uint64_t (*nextULong)(void);

    /**
     * @brief User input boolean
     * @param void
     * @return boolean
     */
    boolean (*nextBoolean)(void);

    /**
     * @brief User input Float
     * @param void
     * @return float
     */
    float (*nextFloat)(void);

    /**
     * @brief User input Double
     * @param void
     * @return double
     */
    double (*nextDouble)(void);

    /**
     * @brief User input Long Double
     * @param void
     * @return long double
     */
    long double (*nextLDouble)(void);

    /**
     * @brief User input String no Spaces
     * @param void
     * @return String
     */
    String (*next)(void);

    /**
     * @brief User input String with Spaces
     * @param void
     * @return String
     */
    String (*nextLine)(void);
}Scanner;
#pragma pack(pop)

Scanner new_Scanner(struct __stdin_t);

#pragma warning(pop)
#endif

Systemout.c

#include "main.h"
#include "System.h"

void print(const String format, ...) {va_list ap;char buf[4096];va_start(ap, format);vsprintf(buf, format, ap);va_end(ap);fprintf(stdout, "%s", buf);}
void println(const String format, ...) {va_list ap;char buf[4096];va_start(ap, format);vsprintf(buf, format, ap);va_end(ap);fprintf(stdout, "%s\n", buf);}

SYSTEM System = {print, println};

Scanner.c

#include "main.h"
#include "System.h"
#include "Scanner.h"

char nextChar(void){
    char c;
    scanf("%c", &c);
    getchar();
    return c;
}

int8_t nextByte(void){
    int8_t b;
    scanf("%hhd", &b);
    getchar();
    return b;
}

int16_t nextShort(void){
    int16_t s;
    scanf("%hd", &s);
    getchar();
    return s;
}

int32_t nextInt(void){
    int32_t i;
    scanf("%d", &i);
    getchar();
    return i;
}

int64_t nextLong(void){
    int64_t l;
    scanf("%lld", &l);
    getchar();
    return l;
}

uint8_t nextUByte(void){
    uint8_t b;
    scanf("%hhu", &b);
    getchar();
    return b;
}

uint16_t nextUShort(void){
    uint16_t s;
    scanf("%hu", &s);
    getchar();
    return s;
}

uint32_t nextUInt(void){
    uint32_t i;
    scanf("%u", &i);
    getchar();
    return i;
}

uint64_t nextULong(void){
    uint64_t l;
    scanf("%llu", &l);
    getchar();
    return l;
}

boolean nextBoolean(void){
    String s = (String)calloc(0, sizeof(char) * 5);
    scanf("%s", s);
    if(atoi(s)|| !strncmp(s, "true", 4) || !strncmp(s, "True", 4) || !strncmp(s, "TRUE", 4)){
        free(s);
        return true;
    }else{
        free(s);
        return false;
    }
}

float nextFloat(void){
    float f;
    scanf("%f", &f);
    getchar();
    return f;
}

double nextDouble(void){
    double d;
    scanf("%lf", &d);
    getchar();
    return d;
}

long double nextLDouble(void){
    long double ld;
    scanf("%Lf", &ld);
    getchar();
    return ld;
}

String next(void){
    String s = (String)calloc(0, sizeof(char) * 4096);
    scanf("%s", s);
    s = (String)realloc(s, sizeof(char) * (strlen(s) + 1));
    getchar();
    return s;
}

String nextLine(void){
    String s = (String)calloc(0, sizeof(char) * 4096);
    scanf("%[^\n]", s);
    getchar();
    return s;
}

/**
* Constructs a new {@code Scanner} that produces values scanned
* from the specified input stream. Bytes from the stream are converted
* into characters using the underlying platform's
*
* @param  source An input stream to be scanned
*/
Scanner new_Scanner(struct __stdin_t source){
    Scanner scanner = {
        .nextChar = nextChar,
        .nextByte = nextByte,
        .nextShort = nextShort,
        .nextInt = nextInt,
        .nextLong = nextLong,
        .nextUByte = nextUByte,
        .nextUShort = nextUShort,
        .nextUInt = nextUInt,
        .nextULong = nextULong,
        .nextBoolean = nextBoolean,
        .nextFloat = nextFloat,
        .nextDouble = nextDouble,
        .nextLDouble = nextLDouble,
        .next = next,
        .nextLine = nextLine
    };
    return scanner;
}

다음으론 User 의 정보를 저장하고 처리할 수 있는 User 클래스를 구현하기 위해 User.h 와 User.c 를 만들었다.

 

User.h

#include "main.h"

#pragma once
#ifndef __User_H
#define __User_H

typedef struct __USERDATA__{
    String id;
    String password;
}USERDATA;

typedef struct __USERS__{
    void (*writeLog)(uint8_t, USERDATA, FILE *);
    boolean (*logout)(USERDATA *);
    boolean (*signup)(USERDATA *, FILE *);
    boolean (*login)(USERDATA *, FILE *);
}USERS;

typedef enum __MENU__{
    SIGNUP = 1, LOGIN, LOGOUT, USERINFO,  EXIT
}__MENU__;

typedef uint8_t MENU;

USERS new_User();
USERDATA new_UserData();
void delete_UserData(USERDATA *);

#pragma warning(pop)
#endif

User.c

#include "User.h"
#include "System.h"
#include "Scanner.h"

import SYSTEM System;

void delete_UserData(USERDATA *info){
    free(info->id);
    free(info->password);
}

void writeLog(uint8_t logCate, USERDATA info, FILE *fp){
    String logCateStr[3] = {"SIGNUP", "LOGIN", "LOGOUT"};
    time_t t = time(NULL);
    struct tm tm = *localtime(&t);
    fp = fopen("log.txt", "a");
    fprintf(fp, "%d-%d-%d %d:%d:%d %s\nID: %s\nPassword: %s\n\n", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, logCateStr[logCate - 1], info.id, info.password);
    fclose(fp);
}

boolean signup(USERDATA *info, FILE *fp){
    Scanner sc = new_Scanner(System.in);
    fp = fopen("user.txt", "r");
    System.out.println("Sign up");
    System.out.print("ID: ");
    info->id = sc.next();
    System.out.print("Password: ");
    info->password = sc.next();

    USERDATA user = new_UserData();
    while (fscanf(fp, "%s", user.id) != EOF){
        if(!strcmp(info->id, user.id)){
            delete_UserData(&user);
            return false;
        }
    }
    delete_UserData(&user);
    fclose(fp);

    fp = fopen("user.txt", "a");
    fprintf(fp, "%s %s\n", info->id, info->password);
    fclose(fp);
    return true;
}

boolean login(USERDATA *user, FILE *fp){
    Scanner sc = new_Scanner(System.in);
    fp = fopen("user.txt", "r");
    System.out.println("Login");
    System.out.print("ID: ");
    user->id = sc.next();
    System.out.print("Password: ");
    user->password = sc.next();
    
    USERDATA info = new_UserData();
    
    while (fscanf(fp, "%s %s", info.id, info.password) != EOF){
        if(!strcmp(user->id, info.id) && !strcmp(user->password, info.password)){
            delete_UserData(&info);
            fclose(fp);
            return true;
        }
    }
    fclose(fp);
    delete_UserData(&info);
    return false;
}

boolean logout(USERDATA *info){
    Scanner sc = new_Scanner(System.in);
    System.out.print("Do you want to logout? (y/n): ");
    char bf = sc.nextChar();
    if(bf == 'y' | 'Y'){
        info->id = NULL;
        info->password = NULL;
        delete_UserData(info);
        System.out.println("Logout success");
        return true;
    }else{
        System.out.println("Logout failed");
        return false;
    }
}

USERS new_User(){
    USERS user;
    user.logout = logout;
    user.signup = signup;
    user.login = login;
    user.writeLog = writeLog;
    return user;
}

USERDATA new_UserData(){
    USERDATA info;
    info.id = (String)calloc(0, sizeof(char) * 20);
    info.password = (String)calloc(0, sizeof(char) * 20);
    return info;
}

마지막으로 클래스들을 활용하여 호출할 메인 클레스를 만든다.

 

main.c

#include "main.h"
#include "System.h"
#include "Scanner.h"
#include "User.h"

import SYSTEM System;

int main(void){
    Scanner sc = new_Scanner(System.in);
    USERS users = new_User();
    USERDATA user = new_UserData();
    FILE *fp;
    boolean isSignup = false;
    boolean isLogin = false;
    boolean isLogout = true;
    MENU menu = 0;

    while (true){
        System.out.print("Select menu\n1. Sign up\n2. Login\n3. Logout\n4. LOGININFO\n5. EXIT\n");
        menu = sc.nextUByte();
        if(menu == SIGNUP){
            isSignup = users.signup(&user, fp);
            System.out.println(isSignup ? "Sign up success" : "Sign up failed");
            if (isSignup) users.writeLog(SIGNUP, user, fp);
        }else if(menu == LOGIN){
            if(isLogin){
                System.out.println("Already login");
            }else{
                isLogin = users.login(&user, fp);
                System.out.println(isLogin ? "Login success" : "Login failed");
                users.writeLog(LOGIN, user, fp);
            }
        }else if(menu == LOGOUT){
            if(!isLogin){
                System.out.println("Already logout");
            }else{
                users.writeLog(LOGOUT, user, fp);
                isLogout = users.logout(&user);
                if(isLogout) isLogin = false;
            }
        }else if(menu == USERINFO){
            if(isLogin) System.out.println("ID: %s\nPassword: %s", user.id, user.password);
            else System.out.println("Please login");
        }else if(menu == EXIT){
            break;
        }else{
            System.out.println("Wrong menu");
        }
    }

    delete_UserData(&user);
    return 0;
}

콘솔에 이렇게 입력하면 실행이 된다.

gcc -c src\main.c -I inc\

gcc -c src\Scanner.c -I inc\

gcc -c src\Systemout.c -I inc\

gcc -c src\User.c -I inc\

gcc -o main main.o Scanner.o Systemout.o User.o

.\main

 

C 언어 로 자바 입출력 문을 구현 해보도록 하자

 

먼저 main.h 를 만든뒤 코드를 작성하자

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <time.h>

#ifndef _MAIN_H
#define _MAIN_H

#define import extern
#define final const
#define null NULL
#define false 0
#define true 1

typedef char *String;
typedef char int8_t;
typedef short int16_t;
typedef int int32_t;
typedef long long int64_t;
typedef unsigned char uint8_t;
typedef unsigned short uint16_t;
typedef unsigned int uint32_t;
typedef unsigned long long uint64_t;
typedef uint8_t boolean;

#pragma pack(push, 1)
typedef struct _System{
    struct _OUT_{
        void (*print)(const String, ...);
        void (*println)(const String, ...);
    }out;
}Sys;
#pragma pack(pop)

#pragma pack(push, 1)
typedef struct _Scanner{
    char (*nextChar)(void);
    int8_t (*nextByte)(void);
    int16_t (*nextShort)(void);
    int32_t (*nextInt)(void);
    int64_t (*nextLong)(void);
    uint8_t (*nextUByte)(void);
    uint16_t (*nextUShort)(void);
    uint32_t (*nextUInt)(void);
    uint64_t (*nextULong)(void);
    boolean (*nextBoolean)(void);
    float (*nextFloat)(void);
    double (*nextDouble)(void);
    String (*next)(void);
    String (*nextLine)(void);
}Scanner;
#pragma pack(pop)

#endif

main.h 에는 클래스 원형을 구조체와 함수포인터로 만들어 준다.

 

이제 Systemout.c 파일을 만들자

#include "main.h"

void print(const String format, ...) {
	va_list ap;char buf[4096];
    va_start(ap, format);
    vsprintf(buf, format, ap);
    va_end(ap);
    fprintf(stdout, "%s", buf);
}
void println(const String format, ...) {
	va_list ap;char buf[4096];
    va_start(ap, format);
    vsprintf(buf, format, ap);
    va_end(ap);fprintf(stdout, "%s\n", buf);
}

Sys System = {print, println};

main.h 에 인클루드한 stdarg.h 를 사용하여 커스텀print 들을 구현한뒤 Sys System 으로 선언한뒤 print, println 을 함수포인터에 대입한다.

 

이제 Scanner.c 를 만들자

#include "main.h"

char nextChar(void){
    char c;
    scanf("%c", &c);
    getchar();
    return c;
}

int8_t nextByte(void){
    uint8_t b;
    scanf("%hhd", &b);
    getchar();
    return b;
}

int16_t nextShort(void){
    uint16_t s;
    scanf("%hd", &s);
    getchar();
    return s;
}

int32_t nextInt(void){
    uint32_t i;
    scanf("%d", &i);
    getchar();
    return i;
}

int64_t nextLong(void){
    uint64_t l;
    scanf("%lld", &l);
    getchar();
    return l;
}

uint8_t nextUByte(void){
    uint8_t b;
    scanf("%hhu", &b);
    getchar();
    return b;
}

uint16_t nextUShort(void){
    uint16_t s;
    scanf("%hu", &s);
    getchar();
    return s;
}

uint32_t nextUInt(void){
    uint32_t i;
    scanf("%u", &i);
    getchar();
    return i;
}

uint64_t nextULong(void){
    uint64_t l;
    scanf("%llu", &l);
    getchar();
    return l;
}

boolean nextBoolean(void){
    String s = (String)malloc(sizeof(char) * 5);
    scanf("%s", s);
    if(atoi(s) >= 1 || strcmp(s, "true") == 0){
        free(s);
        return true;
    }else{
        free(s);
        return false;
    }
}

float nextFloat(void){
    float f;
    scanf("%f", &f);
    getchar();
    return f;
}

double nextDouble(void){
    double d;
    scanf("%lf", &d);
    getchar();
    return d;
}

String next(void){
    String s = (String)calloc(0, sizeof(char) * 1000);
    scanf("%s", s);
    getchar();
    String str = s;
    return str;
}

String nextLine(void){
    String s = (String)calloc(0, sizeof(char) * 1000);
    scanf("%[^\n]", s);
    getchar();
    String str = s;
    return str;
}

Scanner sc = {
    nextChar,
    nextByte,
    nextShort,
    nextInt,
    nextLong,
    nextUByte,
    nextUShort,
    nextUInt,
    nextULong,
    nextBoolean,
    nextFloat,
    nextDouble,
    next,
    nextLine
};

Scanner 의 기능들을 구현한뒤 Scanner 의 함수포인터에 대입을 해준다.

 

마지막으로 ScannerTest.c 파일을 만든뒤 간단하게 테스트를 해보자

#include "main.h"

import Sys System;
import Scanner sc;

int main(void){
    System.out.print("int8_t: ");
    int8_t a = sc.nextByte();
    System.out.print("int16_t: ");
    int16_t b = sc.nextShort();
    System.out.print("int32_t: ");
    int32_t c = sc.nextInt();
    System.out.print("int64_t: ");
    int64_t d = sc.nextLong();
    System.out.print("uint8_t: ");
    int8_t e = sc.nextUByte();
    System.out.print("uint16_t: ");
    uint16_t f = sc.nextUShort();
    System.out.print("uint32_t: ");
    uint32_t g = sc.nextUInt();
    System.out.print("uint64_t: ");
    uint64_t h = sc.nextULong();
    System.out.print("boolean: ");
    boolean i = sc.nextBoolean();
    System.out.print("char: ");
    char j = sc.nextChar();
    System.out.print("float: ");
    float k = sc.nextFloat();
    System.out.print("double: ");
    double l = sc.nextDouble();
    System.out.print("String: ");
    String m = sc.next();
    System.out.print("String with space: ");
    String n = sc.nextLine();

    System.out.println(" ");
    System.out.println("int8_t: %d", a);
    System.out.println("int16_t: %d", b);
    System.out.println("int32_t: %d", c);
    System.out.println("int64_t: %lld", d);
    System.out.println("uint8_t: %u", e);
    System.out.println("uint16_t: %u", f);
    System.out.println("uint32_t: %u", g);
    System.out.println("uint64_t: %llu", h);
    System.out.println("boolean: %s", i ? "true" : "false");
    System.out.println("char: %c", j);
    System.out.println("float: %f", k);
    System.out.println("double: %f", l);
    System.out.println("String: %s", m);
    System.out.println("String with space: %s", n);

    free(m);
    free(n);

    return 0;
}

다른 파일에 있는 기능들을 사용하기위해 extern 을 import 로 매크로 정의한뒤 System 과 Scanner 을 import 한다.

그리고 입력 받은뒤 간단하게 출력하는예제를 작성 해보았다.

 

이제 터미널에 다음과 같은 명령어들을 입력해보자

gcc -c Systemout.c
gcc -c Scanner.c
gcc -c ScannerTest.c
gcc -o Scanner Systemout.o Scanner.o ScannerTest.o
.\Scanner

이렇게 터미널에 입력하면 간단한 예제가 실행됀다.

오늘은 여러 C 파일을 한개의 실행 파일로 만드는 법을 알아보려한다.

 

먼저 C 파일이 a.c b.c main.c 가 있다고 하면 

 

gcc -c a.c

gcc -c b.c

gcc -c main.c

 

콘솔이나 터미널에 위 처럼 입력하면 a.o, b.o, main.o 가 생성이 된다.

 

gcc -o (실행파일 이름) a.o b.o main.o 

 

위처럼 입력한다.

 

그러면 실행파일명.exe 를 실행 해보면 a.c, b.c, main.c 에 있는 함수들이 다 동작하는 것을 알 수있다. 

+ Recent posts