하루동안 c언어를 공부한 결과 어느정도 수확이 있었다. 아마도 모르고 지나갔다면, 나중에 어떤 삽질을 할지 모를 정도이다.
배운 순서가 아닌, 기억에 강하게 남는 순서다.
1. 변수의 선언이 아닌 실제 나누기 분자와 분모가 변수의 타입을 결정한다.
float f;
f = 9/5
printf(f)
를 하면 1이 나온다. f를 float 그릇(데이터 형)에 담아 선언해도, 두번째 줄에서 이미 소수점이 무시되기로 결정됐기 때문.
나눗셈에서 변수의 진짜 유형은 나누기 양 쪽에 있는 숫자에 의해 결정된다.
해결법: f = 9./5 (분자 분모 아무데나 원하는 데이터형으로 표현)
2. 2의 보수
컴퓨터 혹은 cpu가 4비트만을 바라볼줄 아는 바보라고 생각해보자.
3은 0011로 표현된다.
이 때 -3은 표현하는 방법에 1011로, 앞에 부호 1일때를 음수로 하자! 라고 정할 수 있다. 뒤에는 그대로 읽고 말이다. 그런데, 인간의 보통 사고방식이 먹혀들질 않는 컴퓨터에겐 '보수'라는 방식이 더 편하다고 한다. 정확히는, 1011로 -3을 하게 되면, -3 + 3 과 같은 연산을 하면 컴퓨터가 상당히 골치아파하고, 특히나 단순 무식하게 굴러가야하는 cpu에겐 이것도 꽤나 부담이 되서 '설계 원칙'에 부합하지 않았다고 들었다. cpu에서는 덧셈같이 단순한 형식의 연산을 많이 하는 듯 하다.
0011+ 1011 = 1110 맨 앞의 부호는 1로 마이너스를 뜻한다고 쳐도, 그 뒤 세자리 110이니까 6??? cpu가 혼란스러워할 게 예상이 간다. 아 이렇게 하면 되잖아~ 라고 예외적인 경우를 넣는다는 전제는 역시 단순해야 하는 그 원칙을 벗어나는 결정이 되므로, 컴퓨터 과학자들은 더 이상하게 느껴지지만, 컴퓨터에겐 찰떡인 방법을 썼다.
0011의 보수는 2진법 덧셈 연산으로 10000이 되게 만드는 수를 말한다.
그래서, 이 컴퓨터는 10000의 1은 읽지를 못해서 그냥 날려버리고 0000이 된다. 마치, 3에 (-3)을 더한 것과 똑같이 느껴버린다는 얘기다. 듣고보니 묘하게 설득이 된다.
그런가보다. 그런데, 용납이 되지 않는 포인트가 있다.
-3이 3의 보수로 표현될 수 있어서
0011의 보수인 1101이라고 해보자(보수의 계산법은 많이들 설명하니, 그걸 찾아보면 다시 이해가 잘될 거 같다).
그런데, 1101은 13을 표현하는 수이기도 하다. 그래서 스스로 질문했다.
13의 2진법
1101
3의 2진법, -3의 2진법(3의 2의보수)
0011, 1101
13+1=14
1101+0001=1110
-3+1=-2
1101+0001=1110 그래서, 14와 -2 둘 다 결과가 될 수 있단 말이야??
의외로 스스로 간단한 결론을 내렸다. 이 컴퓨터에서 0000 4개의 비트를 읽을 수 있는 능력에서 한 자리를 빼버리자.
4자리면 111인 7까지 읽을 줄 아는 걸로!
3. cpu는 레지스터에서 명령어 를 읽어온다. 가 아니라 명령 주소
cpu는 진짜로 빵을 찍어내는 기계, 피자를 굽는 기계, 아무튼 기계, 기계... 라고만 생각해보자. 이런 기계들은 뭔가를 주렁주렁 매달고 다니지 않는다. 어디에 뭐가 있는지 신경쓰지도 않고 그저 찍어낼 뿐이다.
그래서 데이터는 cpu에 있지 않고, cpu에 데이터를 그 엄청난 연산 속도에 부합하게 물려주는것도 일이다. 연산이 느려지면 cpu는 짜증내고, 사용자는 당장 cpu부터 컴퓨터 전체를 갈아치울거다. 어떤 이유에서든 빠른 컴퓨터를 원했던 과학자들은 cpu에 가까울 수록 cpu에 데이터를 보내는 속도가 빠르다!라는 개념으로 컴퓨터를 만들고 지금도 나도 그렇게 이해한다.
cpu에 가장 가까이 있는 메모리는 '레지스터'. 'L1' 'L2' 'L3' 순서로 이어진다. 한단계 뒤로 갈수록 몇배씩 느려진다고 배웠다. cpu의 연산속도가 말도안되게 빠른 것도 있지만, 이정도 속도 차이면 무시하지 못한다.
참고로 우리가 보통 컴퓨터를 쓸 때 종종 따지는 ram은 이것들 뒤에 순서에 있어 몇배 더 느리다. 하지만 용량은 32MB vs 16GB 로 수천배 차이날거다.
4. 레지스터에는 주소만을 적는데, 사실이 주소도 진짜 주소는 아니다
이 개념이 가장 거부감이 컸는데, 그냥 팩트 그 자체다. 레지스터는 엄청 빠른 대신에 그 속도를 안정적으로 유지하기 위해 극도로 단순한 메모리이며, 극도로 단순한 정보만을 빠른 속도로 담고, 쓴 다음에 버리고, 다음 녀석으로 넘겨받는다. 그래서 레지스터에는 '더하기' 이런 명령어(연산)조차 담지 않고 그저 '(명령어)주소', '(명령어)주소', '(명령어)주소'.... 이런 식으로 그 단순한 명령어가 담긴 메모리(여기가 RAM인지, L2, L3인지는 안배웠다) 주소만을 계속 담고 버린다.
진짜 팩트는 이 주소도 거짓 주소다. 컴퓨터를 할 때에 단 하나의 프로그램만 실행하지 않는 현대의 컴퓨터 이용 실태를 감당하기 위해 컴퓨터는 cpu를 여러개로 갈라놓아 여러 프로그램을 동시에 처리한다. 그 때에 각각의 cpu일꾼(쓰레드)에서 레지스터에서 명령어를 불러오며 같은 주소를 향해서 cpu의 요청이 돌진할 수 있다. 누구 잘못이라기엔, 누군가가 이 주소를 참조하라고 알려주었는지는 안배웠다. 그래서 원인보다 해결책을 보자면, 각각 프로그램이(이런 세세한 역할도 하는 줄은 몰랐다) 옆에서 열일을 해주며, 한 컴퓨터에서 동시에 실행되는 두 개의 프로그램을 돌리는 cpu가 같은 주소를 가리키며 내가 필요한 데이터가 있다고 말하면, 잽싸게 젖병을 바꿔 주듯이 진짜 ram에 전혀 상관없는 빈 주소를 이어준다. 이런 사고상황에서만 그런게 아니라, 항상 이런 행위를 하고 있어 절대 절대로 두 프로세스가 같은 메모리를 조작하며 대참사가 일어나지 않도록 하는 것이라고 한다. (이건 운영체제가 하는 건가, 아니면 컴파일러가 하는 건가?) 가상 메모리와 진짜 메모리(물리 메모리)에 대한 나의 복습이었다.