모두코드 C언어 #1

원본 링크 - 씹어먹는 C언어 시작하기

Table of Contents


공부 수칙

  • 강좌를 다 읽었다면 머리속으로 정리한다. (나는 블로그로 정리할 것)
  • 위 과정이 잘 안된다면, 다시 한 번 읽는다. 잘 모르겠으면, 질문을 남긴다
  • 강좌에 나오는 모든 소스 코드를 직접 타이핑해서 쓴다.
  • 강좌를 따라 만들며, 자기 마음대로 수정해서 원하는 형태로 바꿔본다.
  • 본인이 만든 프로그램을 자부심을 가지고 인터넷에 올려본다.

Before C Language…

  • 보통 첫 언어는 Python이겠지만… 가끔 불쌍한 영혼C로 시작한다… (나도 그랬다)
  • C언어의 특성 상, 컴퓨터에 대한 배경 지식이 없으면, 이해하기 매우 어렵다. (그래서 성적이 별로였다…)
  • C언어는 1972년에 출시된 언어이므로, 확실히 올드한 느낌이 있다.
  • 탄생 목적은 Unix 운영체제를 작성하기 위해서, 즉 시스템 프로그래밍이 주 목적 (컴퓨터 시스템과 밀접)
  • 중요한 특징으로, 배울 내용이 적다는 장점이 있다! (다른 프로그래밍 언어에 비해서, 배우기 쉽다곤 안했다)

컴퓨터는 뭘까?

  • 컴퓨터는 말 그대로 계산기

  • 어떤 연산을 빠르게 처리해주는 역할을 수행

  • 컴퓨터는 명령어를 읽어서 주어진 명령어에 따라 연산을 수행

  • 누가? 어디서? 명령어를 읽으며, 프로그램은 무엇이며, 명령어는 어떻게 작성하는지?

    • 컴퓨터의 연산은 CPU(Central Processing Unit)이 처리합니다.
    • 대략 1초에 10억번 개의 연산을 처리하며, 발열이 심하므로, 보통 쿨러 아래에 설치됩니다.
    • 최근에는 그래픽 관련 연산을 처리하는 GPU가 있지만, 범용적인 명령어들은 CPU에서 처리됩니다.
    • CPU가 명령어를 실행하려면, 명령어를 읽어야하며, 연산된 결과를 저장해야합니다.
    • CPU가 연산을 수행하기 위해 데이터를 저장하는 곳을 Register라고 합니다.
    • 일반적인 연산을 수행할 수 있는 Register는 16개뿐입니다. (64bit CPU 기준)
    • 또한 각 Register는 64bit의 데이터를 담을 수 있으므로, 총 128 Byte만 저장이 가능합니다.
    • 장점이라면 Register는 아주 빠르게 접근할 수 있습니다.
    • 더 많은 공간이 필요할 때는 RAM을 이용합니다. (Random Access Memory)
    • RAM에 명령어들을 저장해놓고 있다가, 연산을 수행할 때, RAM에서 읽어옵니다.
    • RAM의 문제라면, 휘발성 메모리이므로, 전원을 껐다가 키면 사라집니다.
    • 늘 새롭게 모든 것을 실행할 수는 없으므로, 비휘발성 메모리가 필요한데 이와 같은 것이 HDD or SSD가 되겠습니다.
    • 전체적인 흐름을 본다면, CPURAM에 명령어를 저장해두었다가, 꺼내며 연산을 수행하며, 연산 결과에 해당하는 정보를 HDD or SSD에 결과로 저장하는 형태입니다.
    • RAMHDDSSD보다는 빠르지만, 생각보다 그렇게 빠르지 않기 때문에, 직접적인 연산을 수행하지는 않지만, 빠르게 데이터를 불러올 수 있는 Cache라는 공간을 사용합니다.
    • Register는 직접적으로 연산을 수행하기 위한 데이터를 저장하는 공간이며, Cache는 직접적으로 연산을 수행하지는 않지만, 빠른 접근을 위해 사용하는 공간입니다.
    • Cache는 L1, L2, L3로 이루어지며, L1이 가장 빠르고 가장 작으며, L3가 가장 느리지만 가장 큽니다.
    • CPU는 조만간 사용할 데이터를 Cache에 불러오며, RAM에 데이터를 저장할 때도, 바로 RAM에 쓰는 것 보다는 Cache에 써놨다가 RAM에 적는 방식을 사용합니다.
    • 하지만 늘 원하는 데이터를 Cache에 저장해놓을 수는 없으며, 이에 대한 예측 성공에 따라, 속도 차이가 많이나게 됩니다.
    • CPU가 요청하는 데이터가 Cache에 없는 경우를 Cache miss라고 하며, 이 경우에 상당히 시간이 걸리게 됩니다.
    연산 실제 접근 시간 1초로 변환시 시간
    1 CPU 연산 사이클 0.4ns 1s
    L1 Cache 접근 0.9ns 2초
    L2 Cache 접근 2.8ns 7초
    L3 Cache 접근 28ns 1분
    RAM 접근 ~100ns 4분
    NVMe SSD접근 ~25 $\mu$s 17시간
    일반 SSD접근 50 ~ 150 $\mu$s 1.5일 ~ 4일
    HDD 접근 1 ~ 10ms 1 ~ 9달
    서울에서 미국 패킷 전송 시간 180ms 14년
  • 명령어 작성 방법?

    • CPURAM에서 데이터를 읽으려면, RAM의 어디에서 데이터를 읽을지, 말해줘야하며, RAM에 있는 주소를 전달해줘야합니다.
    • 어디에서 읽을지 뿐만 아니라, 어디까지 읽을지도 말해줘야 합니다.
    • 반대로, RAM에 데이터를 저장할 때는, 어디에 저장할 지 말해주면, 전달한 데이터로 변경됩니다. (기존에 있던 데이터는 사라집니다.)
  • CPU가 명령어를 읽어오는 방법

    • Program은 실행하는 명령어와 데이터의 집합
    • Program을 실행하면, RAM에 해당 Program이 올라가며, 그 Program의 시작점을 CPU에게 알려줍니다.
    • 그 이후부터는 CPU가 명령어를 읽어가면 실행하게 됩니다.
    • CPU는 어디에서 명령어를 읽어야할 지를 저장하는 Instruction Pointer를 보관하는 특별한 Register가 있습니다.
    • CPURAM에 명령어가 있는 지, 데이터가 있는 지를 알 수 있는 방법은 없습니다. 단순히 쭉쭉 순서대로 처리하기만 합니다.
  • 여러 개를 처리할 때?

    • 여러 일을 처리하다보면, 메모리 공간이 겹치지 않을까? 하는 생각이 듭니다.
    • 이미 다른 쪽에서 사용하고 있는 메모리 공간에 접근하게 되면, 덮어씌워지니 문제가 될 수 있을 것 같습니다.
    • 실제 동작 시에는 CPU가 확인하는 주소값과, 실제 RAM의 주소값을 동일하게 사용하지 않으며, CPU에서는 가상 메모리를 사용하여, 처리하며, 이를 특별한 변환 과정을 통해 물리 메모리로 변환하여, 실제 저장합니다.
    • 이러한 방식을 Paging이라고 합니다.
    • 변환이 되는 최소 메모리 단위를 Page라고 하며, 보통 1Page는 4KB입니다.
  • Summary

    • 모든 연산은 CPURegister에서 수행되며, 64bit인 경우, 8Byte의 크기를 가집니다.
    • CPU에서 처리하는 명령어와 데이터는 모두 RAM에서 읽어옵니다.
    • Program을 실행하는 것은 HDD에 있는 명령어와 데이터를 RAM에 쓰는 것이며, CPU에는 실행할 첫 명령어의 주소를 전달합니다.
    • CPUCache가 있어서, RAM 접근 횟수를 줄일 수 있습니다.
    • 각각의 Program은 자신이 전체 RAM 공간 전체를 사용하는 것처럼 동작합니다.
    • CPU에서 참조하는 주소값은 가상 메모리 주소값입니다.
    • 가상 메모리 주소값은 각 Program의 페이지 테이블을 통해 물리 메모리 주소값으로 변환될 수 있습니다.

C언어란 무엇인가?

  • C언어를 배워야하는 이유?
    • 좋은 프로그래머가 되려면, 컴퓨터의 내부 원리를 필수적으로 알아야합니다.
    • C언어는 컴퓨터와 연결성이 깊으며, 내부 원리를 이해하는데 큰 도움이 됩니다.
    • 많은 언어들이 C언어에서 파생되어, 다른 언어를 쉽게 습득할 수 있습니다.
    • 상대적으로 적은 양의 내용만 배워도 됩니다.
  • 어떻게 배워야하나?
    • 컴퓨터와 친해지면….
    • 간단한 프로그램을 직접 구현해보는 것
  • C언어를 배우려면 뭐가 필요….?
    • 컴퓨터
    • 머리
    • 노오오오력… (이게 제일 중요)
    • 컴파일러
  • 컴파일러는 뭐야?
    • CompilerCompile하는 것입니다.
    • 컴퓨터는 실제로는 0과 1로 이루어진 기계어만 이해할 수 있습니다.
    • 그런데 프로그래밍은 0과 1로만 하지는 않죠
    • 이와 같이 프로그래밍 언어와 기계어 사이의 변환을 담당해주는 것이 Compiler입니다.
    • Windows에서는 MicroSoft Visual Studio를 이용하여 MSVC를 사용할 수 있습니다.
    • Ubuntu, MacOS에서는 GCC를 사용할 수 있습니다.
  • 첫 C언어 프로그램
#include <stdio.h>
int main() {
    printf("hello world\n");
    return 0;
}
  • Summary
    • C언어는 다른 언어에 비해, 컴퓨터와 가까우며, 이를 통해 많은 것을 배울 수 있음
    • 컴퓨터는 0과 1로 이루어진 기계어만 이해할 수 있으며, 따라서 프로그래밍 언어를 기계어로 변환하는 Compiler가 필요함

C언어 제대로 시작하기

  • 이전 코드 분석
#include <stdio.h>
  • #include는 뒤에 있는 괄호 안에 있는 파일을 프로그램으로 불러온다는 의미
  • #include <stdio.h>stdio.h를 불러온다는 뜻
int main()
  • 이는 main함수를 정의한 것이며, main함수는 모든 C 프로그램이 시작하는 부분을 의미
  • CPUmain의 첫 번째 명령어의 주소 값을 전달받을 것으로 예상됨
  • intinteger의 줄임말로, 함수가 종료될 때, 정수를 반환한다는 뜻
{
  • 중괄호는 main함수의 시작을 알리며, 중괄호로 묶인 부분까지가 main함수임을 의미
  • 중괄호가 열렸다면, 무조건 닫혀야만 오류가 나지 않습니다.
print("hello world\n");
  • printf는 괄호 안의 내용을 화면에 출력해주는 함수
  • hello world라는 내용이 출력되며, \n은 줄바꿈을 의미
  • 글자를 화면에 출력한다는 것은 운영체제에 화면에 글자를 뿌려야 한다는 메시지를 보내야하며, 운영체제는 모니터에 메시지를 뿌린다는 것을 CPU에 얘기해줘야하므로, 꽤나 어려운 과정입니다.
  • 이를 일일이 CPU에 설명하는 대신, stdio.h에 미리 작성되어 있는 printf라는 함수로 사용하는 것
  • stdioStandard Input Output의 줄임말로, 표준입출력을 뜻합니다.
  • .h로 끝나는 파일은 헤더 파일이라고 부르며, 이를 다른 프로그램에서 include해서 사용할 수 있습니다.
;
  • 사실 C언어에서 가장 중요한 것은 ;(세미콜론)입니다.
  • 없으면… 무조건 오류가 발생합니다.
  • 하지만 모든 부분에 있는 것은 아니며, 상황에 맞게 적용해주어야 합니다.
return 0;
  • 0을 반환한다는 뜻
  • int main에서 main함수는 integer 즉, 정수를 반환한다고 했으며, 0을 반환하고 있습니다.
  • 0, 1, 2 등의 다른 숫자도 가능하지만, 일반적으로 0은 정상적으로 프로그램이 종료되었음을 의미하며, 1 또는 -1 등은 오류가 발생했다는 것을 의미하는 용도로 사용합니다.
  • 주석 넣기
    • 주석이란 코드에 설명을 추가하는 것
    • 실제 동작하는 프로그램에 영향을 미치지 않습니다.
    • C언어에서는 두 가지 방법으로 주석을 넣을 수 있습니다.
      • /**/사이에 존재하는 글자들은 주석으로 처리됩니다.
      • //로 시작하는 줄은 주석으로 처리됩니다.
      /* 이 부분은 주석입니다.
      뭘 써도 상관없어요
      여러 줄에 걸쳐서 쓸 수 있어요
      */
      
      // 이건 한 줄에만 적용돼요
      printf("hello world"); // 이렇게도 사용할 수 있어요
      
  • Summary
    • #include <stdio.h>를 이용하여 stdio.h파일을 코드에 추가할 수 있습니다.
    • main함수는 프로그램을 시작하는 부분
    • {}는 함수의 몸체 범위를 지정하는데 사용
    • printf는 화면에 내용을 출력하는 함수
    • return 0;는 0을 반환한다는 의미
    • // or /* */는 주석으로 Compiler가 무시하며, 코드 설명을 위해 적는 부분

숫자 표현 방법?

  • 수는 Number, 숫자는 Digit
  • 수는 물질의 양을 나타내는 단위
  • 숫자는 이를 기록하기 위한 것
  • 숫자를 표현하는 방법을 기수법이라고 함
  • 십진법, 이진법, 16진법
    • 253 = $2 \times 10^2 + 5 \times 10^1 + 3 \times 10^0$
    • 6 = $4 + 2 = 1 \times 2^2 + 1 \times 2 + 0 \times 2^0 = 110_2$
    • 123 = $7 \times 16 + 11 =$ 0x7B
    • 십진법은 0 ~ 9, 이진법은 0 ~ 1, 16진법는 0 ~ F (0 ~ 15)를 각 자리수로 사용
    • 각 자리수는 $10^n$, $2^n$, $16^n$을 의미
    • 숫자 옆에 작게 2가 쓰여진 것은 이진수
    • 0x로 시작하는 숫자는 16진수를 의미
    • 16진수를 이진수로 변경할 때는 각 16진수의 각 자리를 이진수로 변경한 후, 연결하면 됨
    • 반대로 이진수를 16진수로 변환할 때는 4자리씩 끊어서 16진수로 변경하면 됨
  • 컴퓨터 메모리의 단위
    • 컴퓨터 1개의 메모리 소자는 0 or 1의 값을 가집니다. (Bit)
    • 하지만 이는 너무 작으며, 8개의 Bit를 묶어서 1Byte로 나타내며, 주요 단위로 사용합니다. (8Bit = 1Byte)
    • 8Bit로 나타낼 수 있는 수는 0 ~ 255로 0x00 ~ 0xFF까지를 표현할 수 있습니다. (총 256개)
    • WORD 단위는 Register에서 연산이 실행되는 최소 단위를 의미합니다.
    • 컴퓨터에 따라 다르며, 64Bit 컴퓨터의 경우 1WORD는 64Bit = 8Byte가 됩니다.
  • Summary
    • 이진법은 0 ~ 1, 십진법은 0 ~ 9, 16진법은 0 ~ F로 숫자를 표현
    • 1Byte = 8Bit이며, 1Byte는 0 ~ 255까지의 숫자를 표현할 수 있습니다.
    • 컴퓨터는 대부분 4Byte(32Bit) 또는 8Byte(64Bit)단위로 데이터를 처리합니다.

Review

  • C++ 공부를 한다더니 왜 갑자기 C를 하고 있냐… 할 수 있을 것 같다.
  • 그치만 C++ 코테에서 C의 문법을 사용하는 경우가 간혹 있는 것으로 생각되어서, C가 양이 많지 않으니 함께 보는 것이 좋다는 생각이 들었다.
  • 사실 C언어 문법 자체는 이미 한 3번 정도는 봤던터라, 완전 새로운 내용이 있지는 않을 것 같지만, 한번 리프레쉬해주는 차원에서 좋은 것 같다.
  • 컴퓨터 구조에 대해서 조금은 더 상세하게 알 수 있는 시간이었던 것으로 보인다.
  • 다음 내용에서는 조금 더 실제 코드와 연관되는 부분을 설명할 수 있을 것 같다.

comments