<stdlib.h>에 있는 system() 을 사용해서 shell scipt를 실행가능하다!

#include <stdlib.h>

int main()
{
    system("echo HI");
    system("ls");

    return 0;
}

실행결과

 

예시: 쉘 제작해보기

작성해본 코드

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

#define MAX_HISTORY_SIZE 100
#define MAX_COMMAND_LEN 100

// Define command
typedef struct {
    int id;
    char command[MAX_COMMAND_LEN];
} Log;

int cnt = 0;
Log command_log[MAX_HISTORY_SIZE];

void RecordCommand(char* input)
{
    cnt++;
    strcpy(command_log[cnt].command, input);
    command_log[cnt].id = cnt;
}

int main()
{
    while (1)
    {
        char input[100];
        // input command
        printf("SSAFY>_ ");
        fgets(input, sizeof(input), stdin);

        // remove "\n"
        input[strcspn(input, "\n")] = 0;

        // input !_number
        if (input[0] == '!' && atoi(input + 1) >= 0 && atoi(input + 1) < cnt)
        {
            int command_no = atoi(input + 1);
            // printf("%s\n", command_log[command_no].command);
            strcpy(input, command_log[command_no].command);
        }

        // commands
        if (strcmp(input, "date") == 0)
        {
            system("date");
            RecordCommand(input);
            continue;
        }
        if (strcmp(input, "uptime") == 0)
        {
            system("uptime");
            RecordCommand(input);
            continue;
        }
        if (strcmp(input, "ls") == 0)
        {
            system("ls -al");
            RecordCommand(input);
            continue;
        }
        if (strcmp(input, "log") == 0)
        {
            system("dmesg");
            RecordCommand(input);
            continue;
        }
        if (strcmp(input, "exit") == 0)
        {
            break;
        }
        if (strcmp(input, "hclear") == 0)
        {
            cnt = 0;
            continue;
        }

        if (strcmp(input, "history") == 0)
        {
            for (int i = 1; i <= cnt; i++)
            {
                printf("%d %s\n", command_log[i].id, command_log[i].command);
            }
            RecordCommand(input);
            continue;
        }
        else
        {
            printf("ERROR\n");
        }
    }

    return 0;
}

기본문법

Target 타켓

목표 파일 이름, 빌드하려는 최종 결과물

1개 이상의 타겟이 있어야 한다

comment를 실행하며 comment  앞은 반드시 Tab 들여쓰기를 해야한다 (그림에서 노란색으로 표시된 곳)

HI:
    echo "HI"
HELLO:
    echo "HELLO"

 

 

Dependency 의존성

Target을 생성하기 위한 파일 목록

의존성 Target을 먼저 수행하게 됨

 

아래의 경우 HI 를 실행하면 의존성에 HELLO가 있어서

HELLO를 우선 실행하게 된다

HI: HELLO
    echo "HI"
HELLO:
    echo "HELLO"

Variable 변수

앞에 $를 붙이고 소괄호 () or 중괄호 {} 를 붙여서 사용한다.

가독성을 위하여 script 최상단에 작성

MSG1 = "HI"
MSG2 = "HELLO"

HI: HELLO
    echo ${MSG1}
HELLO:
    echo $(MSG2)

Comment 주석

# 을 사용하여 주석을 표시한다.

한줄 주석만 지원한다.

 

특수변수 (자동변수)

  • $@ : Target 이름
  • $^ : Dependecy 목록 전체
  • $< : Dependecy 목록 중 첫 번째
  • ...

연산자

다양한 연산자를 갖는다

  • +=
  • := (simple equl)
    • script 순서대로 현재 기준에서 값
  • = (reculsive equl)
    • 최종 변수 결과 값

단계별 Makefile 제작

함수 2개가 있는 makefile을 단계별로 제작해보자

common.h

#include <stdio.h>

func1.h

void func1();

func1.c

  1 #include "common.h"
  2
  3 void func1()
  4 {
  5     printf("Func1\n");
  6 }

func2.h

void func2();

func2.c

#include "common.h"

void func2()
{
    printf("Func2\n");
}

main.c

#include "common.h"
#include "func1.h"
#include "func2.h"

int main()
{
    printf("main start!\n");
    func1();
    func2();
    printf("main end!\n");

    return 0;
}

 

Makefile 1단계

파일 구성을 바탕으로 dependencies를 작성한다

result: main.o func1.o func2.o
    gcc main.o func1.o func2.o -o result

main.o: main.c common.h func1.h func2.h
    gcc -c main.c

func1.o: func1.c common.h func1.h
    gcc -c func1.c

func2.o: func2.c common.h func2.h
    gcc -c func2.c

clean:
    rm -r ./*.o result

Makefile 2단계 - 변수 추가

  • CC: compiler가 바뀔 때 변경
  • OBJS: 목적 파일 목록
CC = gcc
OBJS = main.o func1.o func2.o

result: $(OBJS)
    $(CC) -o result $(OBJS)

main.o: main.c common.h func1.h func2.h
    $(CC) -c main.c

func1.o: func1.c common.h func1.h
    $(CC) -c func1.c

func2.o: func2.c common.h func2.h
    $(CC) -c func2.c

clean:
    rm -r $(OBJS) result

 

Makfile 3단계 - 특수 변수 추가

  • $@ : Target을 나타냄
  • $^ : 의존성 타겟들을 나타냄
  • $< : 의존 타겟 중 첫번째
CC = gcc
OBJS = main.o func1.o func2.o

result: $(OBJS)
    $(CC) -o $@ $^

main.o: main.c common.h func1.h func2.h
    $(CC) -c $<

func1.o: func1.c common.h func1.h
    $(CC) -c $<

func2.o: func2.c common.h func2.h
    $(CC) -c $<

clean:
    rm -r $(OBJS) result

 

Makefile 4단계 - 컴파일러 옵션 변수 추가

컴파일 옵션 지정 $(CFLAG)

  •  -g : 디버깅 (Trace) 가능하도록 설정
  • -Wall : Warning이 뜨면 Error 처럼 멈추도록 함
  • -O2 : 최적화 2단계 옵션
CC = gcc
CFLAG = -g -Wall -O2
OBJS = main.o func1.o func2.o

result: $(OBJS)
    $(CC) $(CFLAG) -o $@ $^

main.o: main.c common.h func1.h func2.h
    $(CC) $(CFLAG) -c $<

func1.o: func1.c common.h func1.h
    $(CC) $(CFLAG) -c $<

func2.o: func2.c common.h func2.h
    $(CC) $(CFLAG) -c $<

clean:
    rm -r $(OBJS) result

 

Makefile 5단계 - wildcard, 확장자 치환 적용

wildcard 함수

  • 지정된 패턴에 해당하는 파일 목록 갖고오기
  • *.c : 현재 디렉토리 내 모든 .c 파일 가져오기

확장자 치환 사용

  • 변수에 할당된 파일 목록에서 확장자 치환
  • 변수명:[pattern]=[replacement]
  • SRCS의 .c를 .o 변경해서 OBJS에 저장

CC = gcc
CFLAG = -g -Wall -O2
SRCS = $(wildcard *.c)
OBJS = $(SRCS:.c=.o)

result: $(OBJS)
    $(CC) $(CFLAG) -o $@ $^

main.o: main.c common.h func1.h func2.h
    $(CC) $(CFLAG) -c $<

func1.o: func1.c common.h func1.h
    $(CC) $(CFLAG) -c $<

func2.o: func2.c common.h func2.h
    $(CC) $(CFLAG) -c $<

clean:
    rm -r $(OBJS) result

 

Makefile 6단계 - makedepend 유틸리티 및 SUFFIXES 적용

makedepend

입력한 .c 파일을 분석해서 의존성 헤더파일을 등록해주는 make 도우미 유틸리티

 

makedepend 설치

sudo apt install xutils-dev -y

makedepend 실행

makedepend main.c func1.c func2.c -Y

Makefile 하단에 의존성 목록이 자동으로 추가된 것을 확인할 수 있다.

makedepend 적용

makedepend Target 추가

SUFFIXES

  • 파일 확장자와 관련된 규칙을 지정할 때 사용
  • .c파일을 .o 파일로 컴파일하는 것을 뜻함 (default)
  • suffixes 뜻은 접미사로 확장자를 뜻함

.c .o

  • .c 파일을 .o 파일로 변환할 때 실행할 명령을 정의

CC = gcc
CFLAG = -g -Wall -O2
SRCS = $(wildcard *.c)
OBJS = $(SRCS:.c=.o)
SUFFIXES = .c .o

result: $(OBJS)
    $(CC) $(CFLAG) -o $@ $^

.c .o:
    $(CC) $(CFLAG) -c $<

clean:
    rm -r $(OBJS) result

depend:
    makedepend $(OBJS) -Y

 

실행

make depend
make

 

Makefile 7단계 - 파일명 매크로 추가

all

  • Target 파일이 여러개일 때 사용

.c .o: 는 default 값이라서 생략 가능

CC = gcc
CFLAG = -g -Wall -O2
SRCS = $(wildcard *.c)
OBJS = $(SRCS:.c=.o)
SUFFIXES = .c .o
TARGET = result

all: $(OBJS)
    $(CC) $(CFLAG) -o $(TARGET) $^

clean:
    rm -r $(OBJS) $(TARGET)

depend:
    makedepend $(OBJS) -Y

 

make와 cmake를 다루기 전에 gcc 빌드 과정을 간단히 알아보자

gcc 빌드 과정

빌드(build)란?

소스코드 => 실행 가능한 프로그램으로 변환하는 과정

아래의 과정을 거친다

https://medium.com/@laura.derohan/compiling-c-files-with-gcc-step-by-step-8e78318052

전처리 (Preprocessing)

  • 전처리기 지시어 (#include, #define 등)를 처리
  • 헤더 파일을 포함하고 매크로를 확장하며 조건부 컴파일 수행
  • 결과물은 전처리가 끝난 C언어 형태

컴파일 (Compilation)

  • 전처리된 소스코드(.i 파일)을 기계어로 변환 가능한 중간 단계의 어셈블리 코드로 변환
  • 이 과정에서 문법 오류나 타입 관련 오류 등이 발견
  • 결과물은 어셈블리 코드 파일(.s)

어셈블링 (Assembling)

  • 어셈블리 코드 (.s)를 목적파일(Objective File) (.o)로 변환
  • 목적파일은 기계가 읽을 수 있는 이진코드 형태이지만 완전히 실행가능한 상태는 아님 

링킹(Linking)

  • 목적파일(.o)와 필요한 여러 라이브러리 파일을 연결하여 실행 파일 (.out 또는 .exe)을 생성
  • 링커는 프로그램에서 사용하는 모든 함수와 변수가 정의된 위치를 찾아 연결
  • 특정 함수나 변수가 정의되지 않았다면 링킹 오류 발생

 

크게 묶으면

하나의 소스코드를 Preprocessing, Compilation, Assembling을 거쳐서 Objective File을 만들고,

만들어진 Object files + 라이브러리들을 모아 Linking 하는 것이 빌드의 과정이다.

 

예시 진행

간단하게 예시로 진행하자.

폴더를 만들고 해당 폴더  안에 main.c, hyndrome.c, hyndrome.h 를 생성해보자

// main.c
#include <stdio.h>
#include "hyndrome.h"

int main()
{
    printf("I'm main!\n");
    Hyndrome();

    return 0;
}
// hyndrome.h
void Hyndrome();
// hyndrome.c
#include <stdio.h>

void Hyndrome()
{
    printf("I'm HYndrome!\n");
}

 

.c파일을 각각 컴파일, 어셈블링하자

gcc -c main.c
gcc -c hyndrome.c
ls

만들어진 .o 파일들과 라이브러리 함수들을 하나로 Linking하자

gcc main.o hyndrome.o -o go
ls
./go

GCC 는 똑똑해서 굳이 나눠서 작업하지 않아도 동작한다.

만들었던 .o 파일과 실행 파일을 지우고 다시 빌드해보자

rm -r *.o
rm -r ./go
ls
gcc ./*.c
ls
./a.out

 

빌드 자동화 시작

bash shell script (.sh)를 활용해서 위 과정을 다시 진행해보자

build.sh 파일을 생성하고 아래 코드를 작성한다.

#! /bin/bash
gcc -c ./main.c
gcc -c ./hyndrome.c
gcc ./main.o ./hyndrome.o -o ./go
rm -r ./*.o

 

source 로 .sh 파일을 실행할 수 있다.

source build.sh
./go

 

이렇게 shell script로 빌드를 자동화할 수는 있지만

중간에 파일 하나라도 변경점이 생긴다면 모든 파일을 다시 빌드해야하는 문제점이 있다!

make

소프트웨어 빌드를 자동화하는 데 사용되는 도구로, 주로 소스 코드를 컴파일하여 실행 가능한 프로그램으로 만드는 작업을 간소화하기 위해 사용된다

Makefile이라는 특별한 형식의 파일과 문법을 사용한다

 

make 설치

sudo apt install make -y

 

make 사용방법

  1. Makefile 스크립트 파일을 만든다
  2. make 명령어로 실행한다

예제 실습

파일 이름을 Makefile로 아래 코드를 작성한다.

go : main.o hyndrome.o
    gcc main.o hyndrome.o -o go
main.o : main.c
    gcc -c main.c
hyndrome.o : hyndrome.c
    gcc -c hyndrome.c
clean :
    rm -r ./*.o ./go

정상 작동 확인

다시 make를 사용하여 빌드를 하면 변경사항이 없어서 build가 되지 않는 것을 확인할 수 있다.

그러면 hyndrome.c 파일을 조금 수정해서 다시 빌드해보자.

// hyndrome.c
#include <stdio.h>

void Hyndrome()
{
    printf("I'm updated HYndrome!\n");
}

처음 make를 사용해서 빌드했을 때 와 다르게 gcc -c main.c 는 컴파일 안된 것을 확인할 수 있다.

변동사항이 생긴 hyndrome.c, hyndrome.c 변동사항으로 인해 변화가 생긴 hyndrome.o에 연동된 빌드들만 다시 진행된 것을 확인할 수 있다!

make는 특정 키워드로 동작시킬 수 있다

make clean

make 장점

  • build 자동화
    • 기술된 순서대로 build 작업을 수행하는 자동화 스크립트 지원
  • build 속도 최적화
    • 파일간의 의존성을 추적하여 파일이 변경된 경우에만 컴파일한다!

CMake

Makefile을 자동으로 생성해주는 build system

크로스 플랫폼 빌드 자동화 도구

 

CMake 설치

sudo apt install g++ cmake -y

 

CMakeList.txt 파일을 작성한다

cmake_minimum_required(VERSION 3.5)
project(HYndromeProject)

# 빌드할 실행 파일(go)과 소스 파일들을 정의합니다.
add_executable(go main.c hyndrome.c)

# 헤더 파일이 있는 디렉토리를 포함시킵니다.
include_directories(${CMAKE_CURRENT_SOURCE_DIR})

# 실행 파일(go)이 사용하는 헤더 파일(hyndrome.h)을 명시합니다.
target_include_directories(go PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

 

cmake한다

cmake .

 

약 200 줄 정도의  Makefile이 생성된 것을 확인할 수 있다!

 

생성된 Makefile로 빌드해보자!

정상적으로 빌드된 것을 확인할 수 있다.

python으로 코딩을 처음 배운 내게 c언어를 배울 때 가장 머리를 아프게 했던 개념!

포인터는 뭘까?

위키백과에서 정의하는 포인터는 아래와 같다.

포인터(pointer)는 프로그래밍 언어에서 다른 변수, 혹은 그 변수의 메모리 공간주소를 가리키는 변수

 

변수는 대충 값을 저장하는 곳 같은데 메모리 공간 주소는 뭘까?

간단한 c언어 표준 입출력 코드로 알아보자

메모리 공간 주소란?

#include <stdio.h>

int main()
{
    int a; // 변수 a를 정수형(int)으로 선언

    printf("상수 하나를 입력해주세요: ");
    scanf("%d", &a); // 사용자가 입력한 정수를 변수 a에 저장 (a의 주소에 값을 저장)
    printf("입력된 값: %d\n", a); // 변수 a에 저장된 값을 출력

    return 0;
}

변수를 선언하게 되면 컴파일러는 해당 변수의 자료형에 맞는 메모리 공간의 크기를 계산해주고, 운영체제는 해당 코드를 실행할 경우 계산된 메모리 크기에 맞는 메모리 공간을 확보해준다. 그래서 메모리 주소는 그때 그때 달라진다.


쉽게 말하면 선언한 변수에 해당하는만큼 메모리 공간이 확보가 된다!

 

int 자료형의 크기는 4 bytes (환경마다 다를 수는 있겠지만 대부분의 환경에서)
자료형을 간단히 설명하면 컴퓨터는 0과 1로 데이터를 저장하게 되는데 이를 정보의 최소 단위인 bit라고 한다.
따라서 1bit가 나타낼 수 있는 경우의 수는 2가지이다.
1byte는 8bit로 구성되어 있으므로 1byte가 나타낼수 있는 경우의 수는 2 ^ 8인 64가지이다.
따라서 int 자료형인 4 bytes는 2^32가지 경우의 수를 나타낼 수 있는데,

int는 -범위부터 정수를 나타내므로

int가 저장할 수 있는 수의 범위는 (-2^31) ~ (2^31 - 1)까지 범위의 수를 나타낼 수 있다! (양수에 -1이 된 이유는 0을 포함해서 1개가 빠짐)


쉽게 말하면 int 자료형은 4칸만큼의 메모리 공간이 확보가 된다!

 

&연산자는 단항연산자로 사용될 경우 주소 연산자 (Address-of Operator)로 해당 변수의 주소를 가르키게 된다.

 

scanf("%d", &a); 따라서 해당 코드는 a의 주소에 %d (십진수)의 형태로 입력을 받겠다는 코드이다.

 

해당 코드를 실행하고 1를 입력해보자

변수 a에 운영체제와 컴파일러가 0x00000000AC08FF694 라는 주소값을 할당해줬고,

자료형이 int라서 4칸 (4 byte)를 확보한 것을 확인할 수 있다.

 

그렇다면 이론상으로 int 자료형이 저장할 수 있는 범위인 (2^31 - 1) 인 2147483647을 대입해보자.

 

먼저 2147483647를 16진수로 표현해보자.

계산기로 확인해본 결과 2147483647는 16진수로 7FFF FFFF이다.

ff ff ff 7f 형태로 저장되어 있는 것을 확인할 수 있다.

이는 메모리에 리틀 엔디안 방식을 저장하고 있음을 확인할 수 있다.

리틀 엔디안: 바이트 단위로 가장 작은 바이트(LSB)를 먼저 저장하는 방식

 

메모리 공간 주소를  저장하는 포인터

자 그러면 본격적으로 포인터를 사용해보자.

아까 & 연산자는 a의 주소를 나타내는데 사용하였다.

포인터를 사용하여 직접 a의 주소를 포인터에 할당해보자

#include <stdio.h>

int main()
{
    int a; // 변수 a를 정수형(int)으로 선언
    int* p = &a; // 포인터 

    printf("상수 하나를 입력해주세요: ");
    scanf("%d", p); // 사용자가 입력한 정수를 변수 a에 저장 (a의 주소에 값을 저장)
    printf("입력된 값: %d\n", a); // 변수 a에 저장된 값을 출력
    printf("입력된 값: %d\n", *p); // 포인터 p가 가리키고 있는 값을 출력

    printf("코드 끝\n");
    return 0;
}

1을 입력한 결과

포인터인 p가 a의 주소를 가리켜서 a에 값을 저장한다.

*는 포인터 역참조 연산자( Dereference Operator )로 포인터가 가리키고 있는 값을 정상적으로 가리키고 있는 것을 확인할 수 있다. 역참조할 때는 어떤 타입의 포인터인지가 중요하다. 뒤에 설명하겠다.

 

포인터에 타입이 있는 이유

그러면 int의 배열로 선언해보자!

자 이제부터 머리가 슬슬 아프다.

#include <stdio.h>

int main()
{
    int a[3]; // int 3개 크기의 배열 선언
    int* p = &a; // 포인터 

    printf("첫번째 숫자를 입력해주세요: ");
    scanf("%d", p); // 사용자가 입력한 정수를 변수 a에 저장 (a의 주소에 값을 저장)
    printf("두번째 숫자를 입력해주세요: ");
    scanf("%d", p+1); // 사용자가 입력한 정수를 변수 a에 저장 (a의 주소에 값을 저장)
    printf("입력된 값: %d\n", *p); // 변수 a에 저장된 값을 출력
    printf("입력된 값: %d\n", *(p+1)); // 포인터 p가 가리키고 있는 값을 출력

    printf("코드 끝\n");
    return 0;
}

int 3개 크기의 배열을 선언하고 a의 주소값을 int형의 포인터 p에 할당했다.

해당 코드를 실행하고 1과 2를 입력해보았다.

위 내용을 그림으로 표현하면 아래와 같다

꼬아보기

자, 그러면 여기서 조금 더 꼬아서 p의 포인터형을 char*로 바꾸면 어떻게 될까?

아래 상황을 미리 설정하고 해당 상황을 코드로 작성해보자.

주소값에 순서대로 23, 34, 45, 56를 입력하려면 어떡해야할까? 리틀 앤디안을 고려해서 16진법으로 56453423에 해당하는 수를 찾아서 입력하면 된다.

계산기로 16진법으로 56453423 에 해당하는 수는 십진법으로 1447375907임을 확인할 수 있다.

scanf 를 받을 때 %x로 입력을 받으면 16진법으로 바로 입력 받을 수 있다.

리틀 앤디안을 고려해서 순서를 고려해야 한다.

 

코드 작성

#include <stdio.h>

int main()
{
    int a[3]; // int 3개 크기의 배열 선언
    char* p = &a; // 포인터 

    printf("16진법으로 56453423에 해당하는 수를 입력해주세요: "); // 1447375907 입력
    scanf("%d", a); // a를 배열로 선언했을 경우 a는 &a[0]을 의미
    printf("32435465를 입력해주세요: ");
    scanf("%x", &a[1]); // 16진법으로 바로 입력
    printf("첫번째 주소에 입력된 값을 char로 출력: %c\n", *p); // 변수 a에 저장된 값을 출력
    printf("두번째 주소에 입력된 값울 char로 출력: %c\n", *(p+1)); // 포인터 p가 가리키고 있는 값을 출력

    printf("코드 끝\n");
    return 0;
}

결과확인

메모리를 확인해보면 계획했던대로 값이 입력된 것을 확인할 수 있다.

16진법으로 23과 34에 해당되는 값을 확인하면

각각 '#' 과 '4' 인 것을 확인할 수 있다.

포인터의 자료형에 따라 포인터가 이동하는 크기가 달라지는 것을 확인할 수 있었다.

포인터를 선언하고 해당 포인터가 가리키는 주소에 값 할당하기

그러면 자료형 변수를 선언하는게 아니라 바로 포인터를 선언하고 해당 포인터가 가르키고 있는 주소에 값을 바로 넣을 수 있지 않을까?

#include <stdio.h>

int main()
{
    int* p; // 포인터 선언

    printf("상수 하나를 입력해주세요: ");
    scanf("%d", p); // 해당 포인터에 값 넣기
    printf("입력된 값: %d\n", *p); // 포인터가 가르키고 있는 값 출력하기

    return 0;
}

해당 코드의 진행을 보면 정상적으로 진행될 것 같다. int형 포인터를 선언하고 해당 포인터가 가르키는 주소에 값을 입력 값고 그 값을 출력할 것 같다. 하지만 결과는?

초기화되지 않은 변수 p를 사용해 run-time check failure가 발생하였다.

 

int a; 처럼 자료형으로 선언할 경우 해당 자료형에 맞는 메모리 공간이 확보되고 유효한 주소를 갖게 된다. (값은 몰론 초기화 x)

int* p 처럼 포인터를 선언할 경우, 해당 포인터를 저장할 수 있는 메모리 공간은 확보되지만 해당 포인터는 쓰레기 주소값을 가리키고 있기 때문에 유효한 주소를 할당해줘야 한다. 이럴 경우에

 

1. 변수를 선언하고 해당 변수의 주소에 할당해 주거나,

2. malloc을 사용하여 동적으로 메모리 주소를 할당해줄 수 있다. (이경우 메모리 누수가 일어나지 않도록 사용하고 해제해줘야 함)

 

1번은 이미해봤으니 2번으로 해보자.

#include <stdio.h>
#include <stdlib.h>

int main()
{
    int* p = malloc(sizeof(int)); // int 크기만큼 메모리 할당

    printf("상수 하나를 입력해주세요: ");
    scanf("%d", p);
    printf("입력된 값: %d\n", *p);

    free(p); // 동적 메모리 해제
    printf("동적 메모리 해제\n");

    return 0;
}

 

 

실행 결과

정상적으로 포인터 p가 가르키고 있는 주소에 값이 할당된 것을 확인할 수 있다

 

포인터 p를 저장하고 있는 공간은 따로 있으며 해당 주소(&p)에서 가리키는 값이 p의 주소임을 확인할 수 있다.

포인터 p의 크기는 8 bytes인데 이를 통해 운영체제가 64bit임을 알 수 있다.

운영체제가 64bit라는 것은 64bit의 (8bytes)의 메모리 주소를 사용할 수 있음을 뜻한다.

참고로 32bit 운영체제는 32bit의 메모리주소를 사용할 수 있어서 메모리 용량을 2^32 bytes = 4gb까지 사용할 수 있다.

 

free(p)로 동적메모리를 해제한 결과를 확인할 수 있다.


MinGW-w64 설치

파일 설치

https://toopyo.tistory.com/entry/MinGW-w64-HowToInstall

 

[MinGW-w64 설치 방법]Windows에서 GCC, G++ 사용하는 법

수성비전자방입니다. 그동안 GCC 관련 글들을 올린 적이 몇 번 있었는데요, Windows에서 GCC, G++을 사용하려면 MinGW-w64를 설치하면 됩니다. 아무래도 MinGW-w64 설치 방법을 따로 정리하는 것이 좋겠다

toopyo.tistory.com

공식 설치 프로그램이 제대로 작동을 안하는 상태이므로 직업 해당 파일을 다운 받아 경로에 넣어줌

https://sourceforge.net/projects/mingw-w64/files/

 

MinGW-w64 - for 32 and 64 bit Windows - Browse Files at SourceForge.net

Bank-level encryption of your data as well as our granular, role-based permission structure means you can control who has access to your content and share critical business files with confidence. Onehub offers a suite of robust business tools such as virtu

sourceforge.net

  • Architecture
    • 32bit(x86) 컴파일러를 설치하려면 i686을,
    • 64bit(x64, x86_64, AMD64) 컴파일러를 설치하려면 x86_64를 선택합니다.
  • Threads
    • posix는 C++11/C11 멀티스레딩 기능을 활성화
    • win32는 C++11/C11 멀티스레딩 기능이 없음
  • Exception: about Handling

압축 받은 파일을 압축해제한 후 mingw64 폴더를 아래 경로로 이동

  • C:\Program Files
  • C:\Program Files(x86) : 64bit 컴퓨터에 32bit 컴파일러를 설치한 경로

환경 변수 설정

https://toopyo.tistory.com/entry/Windows-environment-variable-path-pathext

 

Windows 환경 변수(Path, PATHETC, 그 외)

수성비전자방입니다. Windows PC에 프로그래밍 도구를 설치하다 보면 '환경 변수', 'Path'등의 용어를 접하게 됩니다. 오늘은 이 용어들이 어떤 의미인지 알아보고 활용해 보겠습니다. 목차 1. 환경

toopyo.tistory.com

  1. 시스템 환경 변수/고급/환경 변수
  2. 시스템 변수의 PATH에 MinGW-w64 폴더를 넣은 경로\bin 경로 추가

Path 더블 클릭

설치 확인

명령 프롬프트 실행하여 gcc -v 명령어 실행하여 버전 출력 잘되는지 확인

VSCode 셋팅

build task 설정

  1. 상단 툴바 Terminal/Configure Default Build Task -> gcc.exe or g++.exe 선택
  2. task.json 파일 생성 -> 코드 교체
{
    "version": "2.0.0",
    "runner": "terminal",
    "type": "shell",
    "echoCommand": true,
    "presentation": {
        "reveal": "always"
    },
    "tasks": [
        //C++ 컴파일
        {
            "label": "save and compile for C++",
            "command": "g++",
            "args": [
                "${file}",
                "-o",
                "${fileDirname}/${fileBasenameNoExtension}"
            ],
            "group": "build",
            //컴파일시 에러를 편집기에 반영
            //참고:   https://code.visualstudio.com/docs/editor/tasks#_defining-a-problem-matcher
            "problemMatcher": {
                "fileLocation": [
                    "relative",
                    "${workspaceRoot}"
                ],
                "pattern": {
                    // The regular expression. 
                    //Example to match: helloWorld.c:5:3: warning: implicit declaration of function 'prinft'
                    "regexp": "^(.*):(\\d+):(\\d+):\\s+(warning error):\\s+(.*)$",
                    "file": 1,
                    "line": 2,
                    "column": 3,
                    "severity": 4,
                    "message": 5
                }
            }
        },
        //C 컴파일
        {
            "label": "save and compile for C",
            "command": "gcc",
            "args": [
                "${file}",
                "-o",
                "${fileDirname}/${fileBasenameNoExtension}"
            ],
            "group": "build",
            //컴파일시 에러를 편집기에 반영
            //참고:   https://code.visualstudio.com/docs/editor/tasks#_defining-a-problem-matcher
            "problemMatcher": {
                "fileLocation": [
                    "relative",
                    "${workspaceRoot}"
                ],
                "pattern": {
                    // The regular expression. 
                    //Example to match: helloWorld.c:5:3: warning: implicit declaration of function 'prinft'
                    "regexp": "^(.*):(\\d+):(\\d+):\\s+(warning error):\\s+(.*)$",
                    "file": 1,
                    "line": 2,
                    "column": 3,
                    "severity": 4,
                    "message": 5
                }
            }
        },
        // // 바이너리 실행(Windows)
        {
            "label": "execute",
            "command": "cmd",
            "group": "test",
            "args": [
                "/C",
                "${fileDirname}\\${fileBasenameNoExtension}"
            ]
        }
    ]
}

단축키 설정

  1. 상단 툴바 File/Preference/Keyboard Shortcuts -> Open Keyboard Shortcuts(JSON) 선택
  2. keybinds.json에서 원하는 단축키로 설정 및 코드 추가
    [
      // 컴파일
      {
        "key": "ctrl+alt+c",
        "command": "workbench.action.tasks.build"
      },
      // 실행
      {
        "key": "ctrl+alt+r",
        "command": "workbench.action.tasks.test"
      }
    ]

VSCode Extension 설치

C/C++ Extension Pack

 

C/C++ Extension Pack - Visual Studio Marketplace

Extension for Visual Studio Code - Popular extensions for C++ development in Visual Studio Code.

marketplace.visualstudio.com

 

+ Recent posts