0과 1부터 Hello world까지

n0wkim·2020년 5월 18일
17

😎서론

18년도에 컴퓨터공학과에 입학하게 되면서 많은 걱정과 기대를 했다.
일단 코딩에 '코'자도 모르고 입학했기 때문에 막연하게 해커에 대한 이미지는
대충 영화에 나오는 이런 이미지였다.

물론 강의 첫 시간에 배운 내용은 저런 환상과는 거리가 멀었다.
바로 "hello world!"를 c언어로 출력하는 것이었는데, 지금 생각하면 되게 별 거 아니지만
아직도 그 때 ctrl + F5를 누른 후 콘솔에 "hello world"가 출력되는 것을 지켜본 기분을 잊을 수가 없다. 물론 영화 속 해커가 되는 첫 걸음이라고 착각하면서 말이다.

감동과 프로그래밍에 자신을 얻었던 첫 수업. 물론 다음 시간부터 자신감은 사라졌다.

다른 언어도 2년 동안 접하면서, 가장 먼저 해보는 것은 여러 책에서도 그렇듯이 hello world가 아닌가 싶다. 그런데, 우리가 사용하는 언어는 어떻게 만들어졌을까?


💻Computer?

우선 컴퓨터의 언어에 대해 말하려면 최초의 컴퓨터가 어떻게 만들어졌는지 알아야 할 것 같다.

컴퓨터의 역사에 대해 검색해 보면 다음과 같이 나온다.

'컴퓨터'라는 낱말은 17세기 초(1613)에 처음으로 나타났다. 천문학 분야에서 막대한 계산이 필요했기 때문에 천문학 분야에서 도입이 시작되었다. 물론 이 당시의 컴퓨터들은 사람이었고, 기계식 계산기와 계산자, 주판. 산가지 등을 이용해 계산했다. 학자들에게 고용된 '컴퓨터'들은 복잡한 계산을 분할하여 여러 명이 달려들어서 차근차근 처리해 나갔다.

문자 그대로 해석하면 우리가 사용하는 컴퓨터는 '계산기'에 불과하다는 말이다. 물론 일반 계산기 보다는 좀 더 흥미롭고 재미있는 일을 할 수 있지만 말이다. 그렇다면 최초의 언어에 대해서 말하려면 우선 "계산기를 어떻게 만들었을까?"에 대한 질문은 필연적일 것 같다.

덧셈은 연산에 있어서 가장 기본이 되는 동작이므로, 우선 덧셈을 수행할 수 있는 기계를 만들고 나면 곱셈도 할 수 있을 것이고, 뺄셈, 나눗셈도 할 수 있을 것이다. 물론 앞서 보았던 나의 환상을 이루게 해 줄 수도 있을 것이다.

어떤 기계를 만들었다고 하고, 어떤 논리에 의해서 수행하도록 해야 하기 때문에 우리는 복잡한 10진수 체계보다 단순한 2진수 체계를 채택하는 것이 더욱 현명한 선택인 것 같다. 물론 논리 회로에서 전류가 통하는 상태와 통하지 않는 상태를 표현하기에 0과 1이 적당하기도 하지만 말이다.

  • 2진수 덧셈표
+01
00001
10110

  • 10진수 덧셈표
+0123456789
00123456789
112345678910
2234567891011
33456789101112
445678910111213
5567891011121314
66789101112131415
778910111213141516
8891011121314151617
99101112131415161718

위의 표를 보면 간단히 알 수 있겠지만 2진수 덧셈표의 경우 단 1가지 경우에만 자리올림 표시(신호)를 주면 되지만, 10진수 덧셈의 경우 자리올림을 하는 경우는 무려 45가지나 된다. 또한 0과 1을 사용하는 것과 다르게 10 개의 수를 사용하기 때문에 전선의 개수도 10개가 될 것이다!
따라서 2진수를 사용하는 데에는 더 이상 의문을 가지지 않기로 하고, 기계에게 덧셈이 주어지면 이 표를 이용해서 덧셈을 하라고 설계하면 계산기는 완성이다.

* 물론 계산기를 실제로 만들려면 AND, OR, NAND 게이트 등 게이트들과 수많은 릴레이가 필요하다.

추후에 컴퓨터의 발전 과정에 대해서도 글을 써보려고 한다.


🤔똑똑하고 효율적으로

자, 이제 0과 1로 우리가 사용하는 언어를 어떻게 표현할 수 있을까? 사실 직관적으로 생각했을때 영어의 경우 알파벳 순서대로 숫자를 부여하면 될 것 같다. 'a'는 1번, 'b'는 2번... 이런 식으로 말이다. 한 번 섹시하게 생각해 보자.

물론 ' '공백도 어떤 값을 할당해야 할 것이고, 대문자의 경우도 따로 처리해야 될 것이다. 대문자를 표기하는 방법은 두 가지 방법이 있는데, 점자처럼 바로 뒤의 문자가 대문자임을 표시해주는 기호를 따로 만들어서 할당하거나,(키보드에 있는 Shift처럼!) 아니면 대문자 자체에 새로운 값들을 일일히 다 할당하는 방법이 있다.

어쨌든 중요한 사실은 0과 1로 표한하는 '비트' 의 형태로 표현해야 한다는 점이다.
현재 사용하고 있는 전세계의 누적된 대부분의 정보는 도서관에 있는 책과 잡지, 신문들처럼 문자의 형태로 저장되어 있다. (물론 음악이나 영상, 그림도 있지만 일단은 논외로 하자.)

그렇다면 가장 중요한 질문은 부호를 만들 때 각각 몇 비트를 사용할 것인가라는 점이다.
일단, 소문자와 대문자를 표현하는데 52개의 부호가 필요하고 숫자 0에서 9까지를 표한하기 위하여 10개의 부호가 더 필요하다. 0에서 9까지 왜 이진수로 표현하지 않고 새로운 부호를 할당하는지는 '문자' 형식의 숫자도 필요하기 때문이다. (c언어로 한 번 쯤 코딩해 본 사람이라면 "1234"와 1234는 다르다는 걸 알 것이다.) 지금까지 62개의 부호를 사용했고, 극소수의 구두점만 사용한다면 64개의 부호만 사용해도 될 것이며, 이는 최소한 6비트 이상이 사용되어야 한다는 의미이다. (26=642^6 = 64 이므로)

하지만 줄 바꿈이나 '$' 처럼 다른 기호들도 필요하기 때문에, 여유를 조금 부려 128개의 문자 정도가 좋을 것 같다. 따라서 답은 7비트가 된다. 또한 서로 통신을 하는 컴퓨터이니만큼 서로 통일된 부호를 모두 같이 이용하는 것이 훨씬 합리적일 것이다. 이러한 방법을 사용함으로써 컴퓨터는 다른 기기와의 호환성을 좀 더 높일 수 있으며 문자로 표현된 정보를 쉽게 교환할 수 있을 것이다.

사실 정답은 이 글을 읽기 시작한 사람들 대다수가 알고 있을 것이고, 들어본 적이 있는 것일 것이다. 바로 ASCII(미국표준부호)이다. 18년도 첫 학기부터 나에게 고통을 안겨주었던 ASCII코드... 1967년에 제정되었으며, 컴퓨터 산업에서 가장 중요한 표준들 중 하나이다. 예외가 있기는 하지만 기본적으로 컴퓨터로 문자를 다루고 있다면 어떤 방식으로든 아스키 부호가 사용되고 있다고 생각하면 된다.

https://www.acmicpc.net/problem/1893/
코린이였던 나를 괴롭혔던 문제로, 풀이법은 매우 간단하지만 그 방법을 생각하기에는 코린이에게는 매우 어려웠고 괴랄했던 것으로 기억된다...

😊ASCII

아스키 문자에 대해서는 구글에 검색하면 알 수 있는 정보가 많기 때문에 일일히 설명하지는 않을 것이고, 이 코드가 왜 중요한지에 대해서만 간단히 설명할 것이다.

https://ko.wikipedia.org/wiki/ASCII 에서 출력 가능한 아스키 문자 표를 볼 수 있다.
여기서 확인할 점은 7비트로 표현되어있다는 점과, 대문자와 소문자가 계획성 있이 할당되어 있고, 부호가 순서 있게 할당되어있다는 점이다. 이러한 특성 때문에 문자로 다시 치환시키거나 알파벳순으로 정렬시키는 등의 작업을 할 때 많은 면에서 편리하다. (앞에서 나를 괴롭혔던 문제 또한 이러한 성질을 이용한 문제이다.)

물론 그런 사람은 없겠지만, 아스키 문자 표를 자세히 본 사람이라면 아직 부호를 더 할당할 수 있다는 사실을 알 수 있을 것이다. (딱 봐도 128개까지는 아니기도 하고😊)


남은 부호들은 '제어 문자표'에 쓰인다.

아쉽게도 위키피디아에는 둘의 순서가 바뀌어서 나와 있어서 이미 정답을 봤겠지만 말이다.

이 문자들은 눈에 보이지는 않지만 다양한 기능을 수행한다. 물론 이해가 안되는 것들도 있지만, 아스키 부호가 개발되던 당시의 전신타자기에서 사용되던 것을 적용한 것이며, 현재에는 사용되지 않는 것이 상당히 많기 때문에 걱정하지 않아도 될 것 같다. 물론 CR나 LF와 같은 반가운 얼굴들도 보인다.

이러한 제어문자를 사용하는 아이디어는 제어문자를 그래픽 문자 중간에 끼워 넣어서 문서의 기본적인 형태를 구성할 수 있도록 하자는 것이다. 아스키 부호를 받아서 한 페이지에 문자를 찍어내는 전신타자기나 간단한 프린터 장비들을 생각해 보면 이해가 편할 것 같다.

예를 들어, 포스트 말론의 'Circles'을 출력한다고 해 보자.

We couldn't turn around
'Til we were upside down
...

각 문자에 맞는 데이터를 ASCII문자표에 맞게 변환하고, 줄 바꿈과 행의 처음으로 돌아가서 출력을 해야 하므로 CR,LF를 첫 줄 마지막 문자인 'd'에 추가한다. 그 후 '를 문자표에서 찾아서 그에 맞는 ASCII 문자로 변환하고...등의 과정을 거쳐 출력되는 것이다.

🙄7bit? 8bit? 문제점?

사실 ASCII 부호가 개발되었을 당시에는 메모리가 매우 비쌌기 때문에, 메모리를 좀 더 아끼기 위하여 아스키 부호를 6비트로 줄이고 소문자와 대문자의 전환은 시프트 부호를 사용해야 한다는 주장도 있었지만, 받아들여지지는 않았다. 또한, 컴퓨터가 8비트 아키텍처를 사용할 것이므로 아스키 부호도 8비트가 되어야 한다는 주장도 있었다. 물론, 지금은 8비트, 즉 한 바이트(byte)를 사용하는 것이 표준이다. 따라서 사실은 7비트이지만, 대부분 8비트 값으로 저장된다. 더 이상 메모리도 부족하지 않거니와, 단위의 통일을 위해서이기도 한다.

또한 아스키 부호가 컴퓨터 산업에서 가장 중요한 표준인 것은 자명한 사실이지만(1967년에 제정된 것이 아직도 쓰이면서 수많은 코린이들을 괴롭히기 때문에-), 완벽하지는 않다. 첫 번째 이유로는 미국 실정에만 맞추어져 있다는 것이다. 아스키 부호에 달러 기호는 존재하지만 영국의 파운드나 한자나 그리스어, 아랍어 등 비영어권 문자들은 말할 필요도 없다. 사실 7개의 비트 수로 수천 개의 문자들을 표현할 수는 없기 때문이다.

그렇다면 한글을 사용하는 우리들이나, 동양의 다른 여러 나라들은 손가락만 쪽쪽 빨면서 울며 겨자먹기로 한글을 이런 bang-sick(방식)으로 사용해야 하는가? 하는 문제점이 발생한다.

다행히도 수십 년에 걸쳐서 수많은 아스키 부호 확장안이 발표되었는데,, 문제는 각 표준에 따라 다른 부분이나 호환성 문제가 발생한다는 점이었다.(역시 하나를 해결하면 다른 문제가 발생하는 재미있는 컴퓨터세계!) 유명한 인코딩 방식으로는 시프트-JIS(일본 산업 표준)이나 DBCS(double-byte character sets)등의 방식이 사용되고 있다. 하지만, 근본적으로 전세계의 언어를 모두 표현하기에 적합하고, 모호하지 않은 명확한 표준이 있다면 좀 더 개발에 편리할 것이라는 공감대로 인하여 새로운 것이 필요할 것이다.

🙆‍♂🙆‍유니코드(Unicode)!

1988년에 주요 컴퓨터 회사들은 아스키 부호를 대체할 유니코드(Unicode)의 개발에 착수했는데, 우리에게는 희소식이겠지만 처음 이 부호를 만드는데 힘 쓴 사람들을 상상하면 불쌍한 마음이 앞서는 것은 사실이다. 그 이유로는 아스키 부호가 7비트 부호인 반면에 유니코드는 16비트 부호이기 때문이다.(아마 야근을 밥먹듯이 하지 않았을까?)

html을 써 본 사람이라면 밑의 코드가 무엇을 의미하는지 알 것이다. 유니코드 또한 이러한 느낌이라고 생각하면 좋을 것 같다. (물론 utf-8은 유니코드와는 조금 다르지만, 따로 설명은 하지 않겠다. 궁금하면 찾아보도록 하자.)

<meta charset="utf-8">

어쨌든, 유니코드는 2바이트(byte)가 필요하며, 나타낼 수 있는 부호의 범위가 0000h에서 FFFFh에 달하는 65,536자를 표현할 수 있다는 것을 의미한다. 이는 컴퓨터 간의 통신을 위하여 세계의 모든 언어를 수용하기에 충분하며, 확장을 위한 방법까지 제공한다.
다행히도 유니코드가 완전히 새롭게 설계된 것은 아닌데, 유니코드에서 부호 0000h부터 부호 007Fh까자의 처음 128문자는 아스키 문자와 동일하다.

그러나 좋은 점만 있는 것은 아닌데, 바로 메모리(용량)의 문제가 대표적인 문제이다. 더 이상 예전과 같이 한 바이트로 저장할 수 없고, 두 배인 2 바이트가 필요하다는 점이다. 1MB의 문서는 더 이상 1MB가 아니라 2MB가 되는 것이다. 그럼에도 불구하고, 문자 인코딩에 있어서 모호함을 없애고 사용할 수 있다면 이 정도 비용은 지불할 의향이 있나 보다.

👨‍💻기계어?

지금까지 어떤 식으로 우리가 사용하는 언어, 숫자, 그리고 기호들을 컴퓨터가 인식하는지 알아봤다. 0과 1부터, 비트, 그리고 아스키 코드까지. 그렇다면 흔히 말하는 '코딩'은 어떤 식으로 진행되는 것일까?

19년도 첫 학기(3학기), 학교에서 '어셈블리프로그래밍'이라는 수업을 들었는데, 그 해답은 어셈블리어에 있었다. 기계어로 프로그램을 작성하고, 기본적인 컴퓨터 구조와 논리에 대해 배우는 과목이었는데, 재미있었고, 1학년 때 C언어로만 코딩하던 나에게는 새로운 충격을 준 과목임과 동시에 전체적인 윤곽을 알려준 과목이었다. 물론, 다시 하라면 하지 않을 것이다.

기계어로 프로그램을 작성하는 것은 2 × 10 따위의 연산을 2를 열 번 더한다거나, 밥을 먹을 때 젓가락 대신 핀셋으로 먹는 것과 같은 느낌이다. 물론 당연히 결과는 같겠지만, 더 많은 시간과 노력이 필요하다는 말이다. 사실 컴퓨터가 할 수 있는 일은 덧셈과 값을 저장하고, 가져오는 이런 단순한 일 밖에 할 수 없기 때문이다.(여러 알고리즘과 빠른 데이터 처리 속도로 여러 일을 할 수 있는 것이다.) 더 자세히 말하자면, 메모리에 있는 숫자를 프로세서로 가지고 오고, 값을 더하고, 결과를 메모리에 적는 이런 단순한 작업을 한다는 말이다.

아주 예전 초기의 컴퓨터에서는 구멍이 뚫린 종이(천공카드)를 이용해서 연산을 했고, 스위치를 조작하여 메모리로 이진 데이터를 넣었다면, 적어도 그때보다는 더 수월하지만 말이다.

기계어 코드들은 대표적으로 MOV, ADD, CALL 과 같은 짧은 니모닉(mnemonic)으로 이루어져 있는데, 각 명령어가 어떤 의미를 띄는지는 쉽게 유추할 수 있다. (물론 여기서 말하는 니모닉은 0과1로 이루어져 있고, 컴퓨터에서 미리 어떤 동작을 할 지 약속되어 있다.)

C언어를 처음 배운다면, 아마도 "Hello, world!"를 출력해 보았을 것이다. 코드는 다음과 같다.

#include <stdio.h>
int main(void){
   printf("Hello, world!\n");
   return 0;
}

그러나 어셈블리어로 코딩을 한다면, 다음과 같을 것이다.

; Sample x64 Assembly Program
; Chris Lomont 2009 www.lomont.org
extrn ExitProcess: PROC   ; external functions in system libraries
extrn MessageBoxA: PROC
.data
caption db '64-bit hello!', 0
message db 'Hello World!', 0
.code
Start PROC
  sub    rsp,28h      ; shadow space, aligns stack
  mov    rcx, 0       ; hWnd = HWND_DESKTOP
  lea    rdx, message ; LPCSTR lpText
  lea    r8,  caption ; LPCSTR lpCaption
  mov    r9d, 0       ; uType = MB_OK
  call   MessageBoxA  ; call MessageBox API function
  mov    ecx, eax     ; uExitCode = MessageBox(...)
  call ExitProcess
Start ENDP
End

위의 코드는 https://software.intel.com/content/www/us/en/develop/articles/introduction-to-x64-assembly.html에서 제공하는 example 코드이다.

;뒤는 컴퓨터에서 인식하지 않는 주석처리된 언어입니다.
간단하게 내용을 설명하자면, 먼저 .data 블록에서 '64-bit hello!''Hello World!' 를 저장하고 시작한다.

.code 블록에서 Start PROC를 보면, 각 줄마다 어떤 동작을 하는지 뒤의 주석으로 설명을 해 주는데, 주목해야 하는 점은 sub,mov,call이다. rsp,rcx와 같은 레지스터에게 대해서는 나중에 기회가 되면 다시 설명하겠지만, 여기서는 그냥 '컴퓨터의 어느 한 부분에 올려놓고 연산을 한다' 정도로만 알고 넘어가면 좋을 것 같다. 그리고 call 명령어는 주석 처리된 부분을 읽어보면 'system libraries'에 있는 'external functions'를 호출해서 동작한다는 것을 알 수 있다.

그리고 이 파일은 .ASM이라는 파일 형식으로 저장되며, 이는 컴퓨터가 0과 1이 아닌 영문으로 이루어져 있기 때문에 0과 1로 변환이 필요하다. 이를 기계어로 바꾸는 작업을 어셈블(assemble) 이라고 하는데, 이전에는 물론 손으로 했었지만, 수행 가능한 기계어 코드가 있는 파일로 변환해 주는 프로그램이 있다면 수월할 것 같다.

어셈블리어의 니모닉과 기계어 간에 1:1 변환을 수행하면 되므로 그렇게 어려운 작업은 아닐 것 같다. 최초로 어셈블러를 만든 사람은 직접 손으로 어셈블해야 했을 테고, 같은 컴퓨터에 사용할 새롭고 좀 더 좋은 어셈블러를 만들 사람은 그 전의 사람이 만든 것을 이용해서 그 프로그램을 어셈블했을 것이다. 그렇게 한 발짝씩 우리에게 편리하게 진화하는 것이다.

😥아직 부족하다!

그러나 아직 완벽하진 않다. 그 이유는 새로운 마이크로프로세서가 나타날 때마다 새로운 어셈블러가 필요하기 때문이다. 예를 들어, A라는 컴퓨터에서 만든 프로그램은 컴퓨터의 한 부분인 레지스터 a를 사용해서 값을 연산한다고 해 보자. 그런데 B라는 컴퓨터에서는 a라는 이름의 레지스터가 없다면? 당연히 B라는 컴퓨터에서는 A에서 만든 프로그램이 작동하지 않게 되는 것이다.

마이크로프로세서 칩의 수준에서 프로그램을 작성하는 것이므로 아주 작은 것 하나 하나에 대해서 모두 신경을 써야 하며, 컴퓨터의 구조에 대해서도 물론 정확하고 방대한 지식을 필요로 하기 때문이다. 또한, 위의 코드에서도 볼 수 있듯이 단순한 프로그램을 작성하는 데에도 많은 명령어를 필요로 하기 때문에 개발로는 적절하지 않다고 할 수 있다.

따라서, 인간의 언어와 비슷한, 고수준 컴퓨터 언어들이 필요하게 되는데, 이 작업은 인간의 의도를 컴퓨터에게 효율적으로 전달하고 명령을 어떻게 정의할 것인지에 초점을 맞추게 된다. 세종대왕이 한글을 창제한 것 처럼 새로운 언어를 만들어 내야 하는, 그런 것이다.

고수준 언어의 문법을 기계어로 변환시켜 줄 수 있는 프로그램인 컴파일러(compiler)를 만드는 것은 기계어와 1:1 대응되는 어셈블러보다는 훨씬 복잡한데, 컴파일러 설계와 구성을 주제로 하는 논문과 책들을인터넷에 검색해 봐도 한가득이다. 물론, 나도 잘 모른다.ㅎ

🤯고수준 언어?

고수준 언어에는 장단점이 있는데, 장점은 당연히도 어셈블리어에 비하여 배우기 쉽고 프로그램하기도 쉽다는 점이다. 고수준 언어로 작성된 프로그램은 어셈블리어와 같이 특정 프로세서에 의존성을 갖지 않으므로, 다른 컴퓨터와 프로그램을 공유할 수 있다는 장점이 있다. 그래서 우리가 코딩을 할 때 프로세서 단위로 생각하면서 머리아파 할 필요가 없는 것이다!

그럼에도 아직 학교에서 어셈블리어를 가르치는 이유는 뭘까.
물론 많은 이유가 있을 수 있겠지만 내가 생각할 때 가장 큰 이유는 컴파일러가 만들어낸 프로그램이 어셈블리어로 만든 프로그램보다 항상 좋다고 말할 수 없기 때문이 아닌가 싶다.

이 말이 무슨 말인지 모르겠다면, 이해가 1초만에 되는 방법이 있다. 당장 visual studio를 켜고 c언어나 어떤 언어로 코딩을 해 보자. 그 후, assembly어로 코드를 변환하는 기능이 있는데, 이를 이용해서 코드를 변환해 본다면, 그 코드의 길이나 효율이 현저하게 어셈블러로 만든 코드보다 떨어진다는 것을 발견할 수 있을 것이다. 파일의 크기도 물론 크고 말이다. (최근에는 컴파일러 또한 정교하게 코드를 최적화하므로 이러한 차이가 줄긴 했다.)

결론을 말하자면, 고수준 언어를 사용하면 더 편하고 빠르게 프로그램을 만들 수는 있지만, 그 프로그램이 가장 빠르고 프로세서를 최고의 효율로 사용한다는 것은 아니다. 현재는 많은 기술의 발전이 있었기 때문에 이 정도 차이는 별 거 아니라고 생각할 수 있겠지만, 아직도 디바이스에 들어가는 프로그램이나 자판기 등에서는 저수준 언어가 사용된다고 한다.

이제는 구시대의 유물과 같은 어셈블리어는 특별한 경우를 제외하면 거의 사용되지 않는데, 앞서 말했듯이 컴파일러가 발전해서 더 정교해졌다는 것이 첫 번째 이유이고, 두 번째로 저장장치와 메모리의 크기가 커짐에 따라 메모리를 적게 쓰는 쪽으로 프로그램하는 필요가 없어진 것도 있다.

👏 마치며

지금은 클래식한 c언어부터, java, javascript나 html등 스크립트 언어까지 새로운 많은 언어들이 많다. 하지만 이러한 언어를 사용할 때, 아무 생각 없이 사용하기보다는 이렇게 언어의 발전, 역사에 대해 알고 사용하면 좀 더 컴퓨터와 친해지지 않을까? 하는 생각을 한다.

끝으로, 이 글을 쓰면서 검색을 하다가 찾은 영상인데, 귀여워서 넣었다.

profile
끙끙대며 배우는 중

9개의 댓글

comment-user-thumbnail
2020년 5월 18일

👏👏 재밌어요

1개의 답글
comment-user-thumbnail
2020년 5월 18일

잘봤어요 ^^

1개의 답글
comment-user-thumbnail
2020년 5월 19일

잘봤습니다~

1개의 답글
comment-user-thumbnail
2020년 5월 20일

이해하기 쉬워요~~ 감사합니다

1개의 답글
comment-user-thumbnail
2020년 5월 22일

우리가 사용하는 언어는 어떻게 만들어졌을까?에 대한 답변을 드리겠습니다.
세종대왕께서 국민들이 공부를 하기 원하여 만들었습니다.

답글 달기