[42Cursus] libft - 학습 내용

eelijus·2022년 12월 2일
0

42Seoul - Cursus

목록 보기
11/11

학습 내용

아래의 모든 내용은 C언어를 기반으로 한다.

💡컴퓨터 메모리, 메모리 릭, 변수, 자료타입, 함수, 포인터, 댕글링 포인터, 널과 널 포인터, const, static, 컴파일, 빌드 과정, 전처리기, Makefile, 컴파일 오류, 런타임 오류, 구조체, 리스트

여러 파일을 가져오는 것은 Pro 사용자가 사용할 수 있습니다 - 당신은 무료로 프로를 시도 할 수 있습니다.

👉 컴퓨터 메모리

프로그램이 실행되기 위해서는 운영체제(OS)가 프로그램의 정보를 메모리에 로드해야 하고, 프로그램이 실행되는 동안 CPU가 코드를 처리하기 위해서는 메모리가 명령어와 데이터들을 저장해야 한다.

프로그램이 운영체제로부터 할당받는 대표적인 공간은 위와 같다.

코드 영역

실행할 프로그래의 코드가 저장되는 공간. 텍스트 영역이라고도 부른다. CPU는 코드 영역에 저장된 명령을 하나씩 처리한다.
프로그램이 시작되고 종료될 때까지 메모리에 계속 남아있다.

데이터 영역

프로그램의 전역 변수와 정적(static) 변수가 저장되는 영역.
프로그램의 시작과 함꼐 할당되며 프로그램이 종료되면 소멸한다.

힙 영역

프로그래머가 직접 동적으로 메모리를 할당(malloc() 또는 new 연산자 사용), 해제(free() 또는 delete 연산자 사용)하는 영역이다. 선입선출(FIFO)의 방식으로 메모리가 인출된다. 왜냐? 힙 영역에서는 메모리가 낮은 주소에서 높은 주소의 방향으로 할당되기 때문.

스택 영역

프로그램이 자동으로 사용하는 임시 메모리 영역이다. 함수 호출 시 생성되는 지역 변수와 매개 변수가 저장되며. 함수 호출이 완료되면 사라진다. 스택 영역에 저장되는 함수의 호출 정보를 스택 프레임(stack frame)이라고 한다. push로 데이터를 저장하고, pop으로 데이터를 인출한다.
후입선출(LIFO) 방식으로 메모리가 인출된다. 왜냐? 스택 영역이 메모리의 높은 주소에서 낮은 주소의 방향으로 할당되기 때문.

오버플로우

  • 힙 오버플로우 : 힙이 스택을 침범하는 경우
  • 스택 오버플로우 : 스택이 힙을 침범하는 경우

힙은 메모리 위쪽 주소부터 할당되고, 스택은 메모리 아래쪽 주소부터 할당되기 때문에 각 영역이 상대 영역을 침범하는 일이 발생할 수 있다.

변수(variable)

컴퓨터가 명령을 처리하는 도중 발생하는 값을 저장하기 위한 메모리 공간으로, 변할 수 있는 값을 담는다(저장한다).

메모리 릭

응용 프로그램에서 데이터를 메모리에 올렸다가, 이것이 쓸모없어지는 시점에서 적절하게 제거되지 않는 것을 일커는다. 프로그램이 필요하지 않은 메모리를 계속 점유하고 있는 현상.

malloc()을 사용해 동적 할당한 메모리를 free()로 적절히 해제하지 않으면 프로그램이 종료되어도 해당 메모리 공간을 다시 사용할 수 없다. 이런 메모리 누수가 누적되면 결국 시스템 전체의 메모리 부족 현상이 발생할 수 있다.

또한 시스템을 전박적으로 불안정하게 만드는 것 외에 보안의 문제도 발생한다. 메모리 릭이 있는 프로그램이 공격받아 중단되거나, 임의의 코드가 실행될 수 있으며 메모리 부족 시 발생하는 예기치 못한 동작을 이용한 시스템이 공격에 노출될 수 있다.

👉 자료 타입

size_t란?

플랫폼마다 다른 bit의 부호없는 정수형을 사용하기 위해서 사용하는 typedef, 이론상으로 가장 큰 사이즈를 담을 수 있는 unsigned 데이터 타입

임의의 메모리에 바이트 단위로 접근해 값을 다룰 때에는 반드시 unsigned 키워드를 사용해야 제대로된 값을 얻을 수 있다. 왜?

unsigned char를 사용하는 이유 : unsigned는 내부 비트의 일부를 부호를 표현하기 위한 비트로 사용하지않고 모든 bit를 값을 표현하기 위한 용도로 사용을 함. 따라서 임의의 메모리에 바이트 단위로 접근해 값을 다룰 때에는 반드시 unsigned 키워드를 사용해야 제대로된 값을 얻을 수 있다.

👉 함수의 작동 원리

함수 선언 후, 함수 호출

C언어는 절차지향언어기 떄문에 위에서 아래 방향으로 호출된다. 만일 main 함수에서 호출 된 함수가 main문 보다 먼저(위에) 선언되어있지 않으면 (컴파일러가 컴파일 한 적 없는 함수면) 컴파일 에러가 발생한다.

👉 포인터

댕글링 포인터(Dangling Pointer)

외부함수에서 선언된 변수를 동적할당을 하지않고 반환하여 사용하게 된다면 댕글링 포인터(dangling pointer)가 일어나기 때문에 위험합니다.

이미 해제된 메모리 영역을 가리키는 포인터.

  • 문제

메모리 해제 후 해제된 메모리에 접근하려 하거나, 함수 호출에서 자동 변수를 가리키는 보인터를 반환할 시 아래와 같은 문제가 발생할 수 있다./

메모리 접근 시 예측 불가능한 동작

메모리 접근 불가 시 segmentation fault

잠재적인 보안 위험

  • 예방

메모리 해제 후 포인터를 NULL로 설정

해제된 메모리를 특별한 값으로 덮어쓰기

👉 널과 널 포인터의 차이

0 ::'\0':: NULL

숫자인 0

0의 아스키 문자값인 '\0'

0으로 define된 매크로 상수 NULL

  • 널 문자열

    값이 아무것도 들어있지 않은 빈 문자열을 의미. 값이 아무것도 없다고 크기 없는 것은 아니다. 널 문자를 저장하기 위한 공간이 필요하기 때문

    char str[] = "abcd";

    printf("%d", sizeof(str));

    여기서 str배열에는 a,b,c,d,\0이 들어간다. 이 배열을 가지고 sizeof 연산을 해보면

    크기가 4가 아닌 5가 나오는 것을 알 수 있다.

  • 널 포인터

    char *ptr = NULL;

    여기서 ptr은 널 문자를 가리키는 포인터가 아니다.

    char형 포인터 ptr에 NULL이 할당되도록 선언한 것. 즉 말그대로 널 포인터.

    ptr이 가리키는 0번지 주소는 아무 곳도 가리키지 않는 주소를 의미한다. 왜냐? 대부분의 플랫폼에서 0번지는 ROM이거나 시스템 예약 영역에 해당되므로 응용 프래그램이 0번지에 어떤 값을 저장하거나 읽을 수 없도록 보호되기 때문.

​ 1. char *str = "";

​ 2. char *ptr = 0

위에서 str은 널 문자열을 가리키는 포인터이고, ptr은 0번지를 가리키는 널포인터이다.

1번 코드에서 메모리에 널 문자를 위한 공간이 생성된 뒤 널 문자가 할당되고, 그 주소를 str이 갖고 있는 것.

  • 출력 형태

    int null = 0;

    printf("%c, %d, %s", null, null, null);

    위 코드를 실행하면 각각 공백, 0, 0번지의 의미인 (null)이 출력된다.

👉 Const & static

  • (const char *) : 상수형 문자에 대한 포인터. 포인터가 가리키는 변수의 값을 바꿀 수 없음
  • (char const *) : 문자에 대한 상수형 포인터. 포인터 값을 바꿀 수 없음.
  • (const char const *) : 상수형 문자에 대한 상수형 포인터. 포인터의 값과 포인터가 가리키는 변수의 값을 바꿀 수 없음.

해당 식별자의 값을 변경할 수 없도록 선언한다는 키워드

매개변수에 const를 붙이는 이유?

특정 함수에 전달하는 포인터의 내용이 훼손되는 것을 방지하기 위해서이다. 다른 함수에 포인터가 전달될 경우, call-by-reference 형태로 전달되므로 다른 함수 내에서 포인터의 값을 임의 조작할 수 있기 때문이다.

👉 C언어의 컴파일 과정

👉 C언어의 빌드 단계

  1. 전처리(preprocessing)
  2. 컴파일(compile)
  3. 어셈블(assembling)
  4. 링크(linking)

소스 코드(.h, .c) -> 전처리기 -> 혹장된 소스코드(트랜스레이션 유닛) -> 컴파일러 -> 어셈블리 코드(.s) -> 어셈블러 -> 오프젝트 코드(.o) -> 링커 -> 머신 코드 실행파일(.exe, .out)

컴파일 단계 / 링킹 단계

전처리 단계

  • 입력: C언어 코드(.c)

    출력: 트랜슬레이션 유닛(translation unit)

  • 전처리 단계에서는 트랜슬레이션 유닛(소스코드를 확장시킨 것)

    을 만듭니다.

    1. 주석 제거(#include)
      • < > : 시스템 경로에서만 헤더파일을 검색
      • “ ” : 현재작업중인 디렉토리에서 우선 검색한 뒤, 시스템 경로에서 검색
    2. 매크로를 복붙 확장
    3. 인클루드(#include) 파일들을 복붙 확장

컴파일 단계

  • 입력: 트랜슬레이션 유닛

    출력: 어셈블리어 코드(.s)

  • 어셈블리어 코드는 아직 정의를 모르는 심볼을 사용할 수 있습니다.

    • 심볼(symbol): 함수나 변수의 이름 등
    • 헤더를 통한 선언만으로 컴파일이 가능
    • 정의를 모르는 것들은 구멍으로 그대로 남겨둠(나중에 링크 단계에서 메꿔줌)
  • 이 단계 이후부터 코드는 특정 플랫폼에서만 동작

    (C언어가 크로스 플랫폼이라는 주장은 컴파일되기 전까지 입니다)

  • 어셈블리어는 기계코드와 거의 1:1 대응하는 언어입니다.

  • 어셈블리어 코드

    보는 법

    • -S플래그를 사용하면 어셈블리어 코드가 (.s)파일에 저장 됩니다.

어셈블 단계


  • 입력: 어셈블리어 코드(.s)

    출력: 오브젝트 코드(.o)

  • (.c)파일에서 컴파일하는 것보다 (.o)파일을 컴파일 하는 것이 속도가 빠르다.

    • (.c)파일이 많으면 하나하나 구멍을 메꾸는과정과 함수 중복체크를 하는데 까다롭다.

    • (.c)파일을 하나씩 컴파일해서 오브젝트 파일로 저장

      (파일을 수정해야할 때 효율적인 방법)

    • 하지만 프로젝트 컴파일을 빠르게 하기위해 C파일을 하나로 합치기도 한다고 합니다.

      (파일을 수정해야할 때 비효율적)

  • 오브젝트파일을 모아 (.a)라이브러리파일로 만들 수 있습니다.

  • 오브젝트 코드

    보는 법

    • -c플래그를 사용하면 오브젝트파일(.o)이 생성됩니다.
    • (.o)오브젝트 파일은 일반 텍스트파일 편집기로는 볼 수 없기 때문에 “16진수 편집기”로 봐야합니다.(HexEdit, Hxo)

링크 단계

  • 입력: 모든 오브젝트 코드들(.o)

    출력: 최종실행 파일(.exe, .out)

  • 모든 오브젝트 코드들을 모아서 구멍을 메꾼 뒤 실행파일로 저장합니다.

    • 만약 선언(구멍)만하고 그 함수를 제대로 구현을 하지않으면 오류메시지를 출력합니다.
    • 그 함수나 변수가 없어 실행할 방법이 없기에 오류가 납니다.

👉 컴파일러

컴파일을 자동으로 수행해주는 소프트웨어. 컴파일 : 어떤 언어의 코드 전체를 다른 언어로 바꿔주는 과정. 즉 A 프로그래밍 언어로 쓰여진 소스 파일을 B 프로그래밍 언어로 바꿔주는 번역기인 셈.

대개는 고수준 언어를 기계어로 번역하는 프로그램을 일컫는다.

👉 전처리기 (Preprocessor)

💡 https://ko.wikipedia.org/wiki/C_전처리기

명령어 처리를 수행하기 위해 사전 준비적인 계산을 행하는 프로그램.

매크로 : 컴파일러에게 코드의 특성을 알려주는 키워드

  • 전처리문 예시
#include <stdio.h>
#define ID sujilee

내부 소스코드 버전 관리, 라이브러리 import, 함수 정의, 상수 정의 등 다양한 목적으로 사용.

헤더파일?

  • 여러 소스코드 파일에 공통적으로 필요한 것들을 저장해 두는 파일입니다.

    (함수 선언, 매크로, extern 변수 선언 등..)

  • #include로 헤더파일을 호출이 가능합니다.

  • 전처리과정을 거쳐서 헤더파일의 내용이 확장되어 작성됩니다.

👉 Makefile이란?

1. Makefile의 정의

- linux상에서 반복 적으로 발생하는 컴파일을 쉽게하기위해서 사용하는 make 프로그램의 설정 파일이다.

- Makefile을 통하여 library 및 컴파일 환경을 관리 할수 있다.

  • linux에서 반복 적으로 발생하는 컴파일을 쉽게 하기위해 사용하는 make 프로그램의 설정 내용이 적힌 기술 파일입니다. (윈도우의 경우 mingw(윈도우용 gcc패키지)를 다운받고 mingw32-make.exe로 사용하면 됩니다.)
  • make를 이용하면 반복적 명령의 자동화로 시간절약 및 실수 최소화할 수 있고, 프로그램 관리가 용이하다는 장점이 있습니다.
  • 이번 Makefile은 최종적으로 오브젝트파일(.o)들을 모아 libft.a(나만의 라이브러리)를 만들도록 구현할 계획입니다. 굳이 오브젝트파일(.o)로 만든뒤 라이브러리로 묶어주는 이유는 ( C언어 빌드과정 )포스트에서 “어셈블 단계”의 내용을 참고하면 됩니다.
  • 파일명을 Makefile로 생성해주면 됩니다.

Makefile 매크로 작성

  • Makefile의 매크로 설정함수에서의 상수 선언과 비슷한 개념입니다.

  • 반드시 필요한 것은 아니지만 기존 매크로의 기능과 같이 가독성이 좋아지고 수정이 편리해진다는 장점이 있습니다.

  • 3. Makefile의 매크로 정의란 무엇인가?

    - Makefile에서 미리 정의 되어있는 환경 변수라고 생각하면된다.

    - 2. 기본 구조에서 CC = gcc 에서 CC가 매크로이다.

👉 오류

경계를 벗어나 배열을 쓰거나 읽는 것은 정의되지 않은 행동(undefined behavior)이다. 컴파일러는 이의를 제기하지 않음.

컴파일 오류

프로그램 컴파일 시 발생하는 에러.

런타임 에러

프로그램 실행 시 발생하는 에러. ex) 무한 루프

오류 관리에 관한 정보 : https://www.ciokorea.com/news/167460

👉 구조체 (structure type)

사용자가 새롭게 정의할 수 있는 사용자 정의 타입(자료형)

배열이 같은 타입 변수의 집합이라고 한다면, 구조체는 다양한 타입의 변수 집합들을 하나의 타입으로 나타낸것.

구조체는 구성하는 변수를 멤버 혹은 멤버 변수라고 칭함

typedef

이미 존재하는 타입에 새로운 이름을 붙일 때 사용

👉 리스트

선형 형태를 가지고 있는 자료구조. 배열 리스트와 연결 리스트가 있다.
리스트를 구성하는 노드는 보통 내용과 다음 노드를 가리킬 주소값을 멤버로 가진다.

profile
sujileelea

0개의 댓글