스터디/C방장

1. 컴퓨터 구조의 간단한 이해 [C언어 포인터 어려워도 제대로 이해하기]

SW PLAN B 2021. 1. 17. 01:40
728x90

C언어의 정석, C언어의 재 정립

C언어 독학을 하시는 분들께는 처음부터 올바른 개념을 잡아드리고 C언어의 문법과 사용법, 현업에서 실제로 어떻게 쓰이는지 알고 싶은 분들께 제가 알고 있는 C에 관한 지식과 팁을 나눕니다.

잘 이해가지 않거나 아직 다루지 않은 개념에 대해서 카카오톡 오픈 채팅방에서 질문도 받습니다.

C방장 QR코드
C방장 오픈채팅방 QR코드

C방장 오픈 채팅방 입장하기


포인터 어려워도 제대로 이해하기 시리즈 1장 컴퓨터 구조의 이해 대표이미지

들어가기에 앞서

C언어를 처음 공부하는 많은 분들이 포인터를 어려워합니다. 그 이유는 대부분 C언어를 처음 배울 때 컴퓨터 구조에 대한 이해가 없기 때문입니다. C언어는 기계어에 가까운 low level 언어이기 때문에 C언어를 제대로 이해하려면 전공자만큼은 아니더라도 컴퓨터 구조에 대한 이해가 필수적입니다.

간혹 포인터를 쓰면 위험하다고 하는 사람들이 있습니다. 잘 모르고 포인터를 쓰면 실수를 하고 버그를 유발할 수 있다는 것은 맞습니다. 하지만 간혹 이 말을 오해해서 포인터 자체가 위험하기 때문에 사용하면 안 된다고 이해하는 사람들이 있습니다. 그렇지 않다고 자신 있게 말씀드립니다.

C언어를 다루기 위해 포인터를 사용하는 것은 선택이 아니라 필수입니다. C언어를 사용하여 개발한다는 뜻은 low level 언어로 하드웨어를 긴밀하게 제어하겠다는 뜻이기 때문입니다. 어렵겠지만 인내를 갖고 포인터에 대한 이해를 하고 가시면 좋겠습니다.

여담이지만 저는 학부 시절 컴퓨터 구조 수업을 듣고 프로그래밍과 컴퓨터 관련 공부에 흥미를 갖기 시작했습니다. 주변 친구들은 컴퓨터 구조를 딱딱하고 재미없어 했지만 저는 논리적으로 움직이는 컴퓨터의 매력을 느끼게 되는 계기였습니다. 지금 이 글을 읽는 여러분도 그런 계기가 되었으면 좋겠습니다.

컴퓨터 구조의 이해

컴퓨터는 기본적으로 마이크로아키텍처(Microarchitecture)명령어 집합 구조(Instruction set architecture, ISA)로 이루어져 있습니다. 이번 포스팅에서는 포인터를 이해하기 위한 수준 정도로 아주 간단히 컴퓨터 구조를 설명드리겠습니다.

포인터 어려워도 제대로 이해하기 시리즈 1장 컴퓨터 구조의 이해 컴퓨터 구조
그림1. 간단한 마이크로아키텍처 예시

아주 간단한 마이크로아키텍처의 예입니다. 포인터를 이해하기 위해 복잡한 것 들은 빼고 CPU와 Memory에 대한 설명을 드리겠습니다.

CPU(Central Processing Unit)는 우리말로 중앙 처리 장치라고 하며 컴퓨터에서 필요한 연산을 수행하는 역할을 합니다. CPU에서 연산을 수행(execute)하기 위해서는 CPU가 명령어(instruction)를 읽고(fetch) 해석(decode)해야 합니다. 그리고 연산을 위한 데이터는 반드시 CPU 내부에 있는 레지스터에 있어야 합니다. CPU 입장에서 Memory는 외부에 위치하기 때문에 원하는 데이터를 레지스터로 가져오기 위해 주소(address)를 통해 메모리에 접근합니다.

포인터 어려워도 제대로 이해하기 시리즈 1장 컴퓨터 구조의 이해 ISA
그림2. 간단한 ISA 예시

명령어 집합 구조(Istruction Set Architecture, ISA)는 마이크로아키텍처에서 실행되는 명령어들의 집합입니다. 윈도우에서 32-bit, 64-bit 컴퓨터라고 하는 말은 이 명령어를 표현하는 크기를 의미합니다. 32-bit 컴퓨터는 명령어 하나의 크기가 32-bit이고 64-bit 컴퓨터는 명령어 하나의 크기가 64bit 입니다.

그림2는 설명을 위한 명령어들의 예시입니다. 어셈블리 명령어라고도 하며 이 명령어들은 0과 1로 표현되는 기계어와 대응하게 됩니다.

Ra, Rb, Rc는 레지스터 이름이 들어갑니다.

[Ra]는 메모리에 저장된 값을 의미하고 Ra에 저장된 값이 주소가 됩니다.

mov Ra Rb

Rb에 있는 값을 Ra에 복사합니다.

ldr Ra [Rb]

Rb에 저장된 값을 주소로 보고 메모리에서 그 주소에 저장된 값을 Ra에 복사합니다.

str Ra [Rb]

Rb에 저장된 값을 주소로 보고 메모리에 Ra에 저장된 값을 복사합니다.

add Ra Rb Rc

Ra에 Rb + Rc 값을 저장합니다.

sub Ra Rb Rc

Ra에 Rb - Rc 값을 저장합니다.


※ tip

1. 명령어 크기에 따라서 접근 가능한 주소 범위가 달라집니다. 32-bit로 표현할 수 있는 주소 범위는 4GB인데요. 따라서 32-bit 컴퓨터는 4GB 보다 큰 메모리(램)를 인식하지 못합니다.

2. 명령어 크기가 int의 사이즈를 결정합니다. 16bit 컴퓨터에서 int 형은 2Byte로 계산되고 64bit 컴퓨터에 8Byte로 계산됩니다.

2020/12/27 - [스터디/C방장] - 정수형 type 이야기 [C언어 파헤치기 1]

 

정수형 type 이야기 [C언어 파헤치기 1]

제가 알고 있는 C에 관한 지식을 나눕니다. 카카오톡 오픈 채팅방에서 질문도 받습니다. C방장한테 질문하러 가기 ■ QUIZ 1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 int main(void) {     int a = 0;   ..

sw-daily.tistory.com


간단한 컴퓨터 동작 예시

void add(void)
{
    int a = 1;
    int b = 2;
    int c = a + b;
}

위 코드를 컴퓨터가 어떻게 실행하는지 간단한 예제를 통해 살펴보겠습니다.

포인터 어려워도 제대로 이해하기 시리즈 1장 컴퓨터 구조의 이해 더하기 프로그램
그림3. 간단한 더하기 프로그램

프로그램은 명령어들의 집합이라는 말을 많이 들어보셨을 겁니다. 그림3.은 더하기 프로그램을 앞에서 살펴본 명령어들로 구현한 것입니다. C언어로 프로그램을 구현하면 컴파일러는 이 것을 기계어로 변환하는 과정에서 위와 같은 명령어들의 집합을 만들어 냅니다.

이 프로그램은 두 수를 더하는 프로그램입니다. 1+2를 수행하고 그 결과를 메모리에 저장하는 것 까지 컴퓨터가 어떻게 동작하는지 살펴보겠습니다.

편의를 위해 메모리의 0x00000000번지에 값 1이, 0x00000004번지에는 값 2가 저장되어있다고 가정하겠습니다.

가장 오른쪽은 현재 실행되고 있는 명령어를 빨간색으로 표시하였고 순차적으로 실행됩니다. CPU 내부의 하늘색 박스는 현재 실행 중인 명령어를 보여줍니다.

포인터 어려워도 제대로 이해하기 시리즈 1장 컴퓨터 구조의 이해 프로그램 동작1
그림4. mov r0 #0x0

mov r0 #0x0

#0x0에서 #은 상수를 의미합니다. 그림4를 보면 r0에 0x00000000이 적힌(wirte) 것을 볼 수 있습니다. 0x00000000번지에는 더하기의 첫 번째 피 연산자(operand)로 쓰일 1이 저장되어있고 이 값을 레지스터로 불러오기 위해 그 주소를 먼저 레지스터에 적는 것입니다.

포인터 어려워도 제대로 이해하기 시리즈 1장 컴퓨터 구조의 이해 프로그램 동작2
그림5. ldr r1 [r0]

ldr r1 [r0]

r0에 있는 값을 주소로 보고 메모리에서 그 주소의 값을 r1에 불러옵니다(load). 초록색으로 표현한 메모리를 보면 0x00000000번지에는 0x00000001이 저장되어있습니다. 따라서 r1에 0x00000001이 저장되고 나중에 더하기 연산의 피 연산자로 사용될 것입니다.

포인터 어려워도 제대로 이해하기 시리즈 1장 컴퓨터 구조의 이해 프로그램 동작3
그림 6. mov r0 #0x4

mov r0 #0x4

두 번째 피연산자를 레지스터로 복사해오기 위해 두 번째 피연산자가 있는 주소 0x00000004를 r0에 복사합니다.

포인터 어려워도 제대로 이해하기 시리즈 1장 컴퓨터 구조의 이해 프로그램 동작4
그림 7. ldr r2 [r0]

ldr r2 [r0]

r0에 저장된 값을 주소로 보고 메모리에 있는 값을 r2로 불러옵니다(load). 

포인터 어려워도 제대로 이해하기 시리즈 1장 컴퓨터 구조의 이해 프로그램 동작5
그림 8. add r3 r1 r2

add r3 r1 r2

r1과 r2에 있는 값을 더해 r3에 적습니다(write). 더하기 연산은 add 명령어에서 끝났지만 그 결과는 아직 레지스터에 남아있습니다. 레지스터는 메모리에 비해 가격이 비싸고 물리적 공간을 많이 차지하기 때문에 메모리만큼 많이 구현할 수 없습니다. 따라서 이 연산 결과를 레지스터에 남겨두는 것이 아니라 나중에 사용하기 위해 메모리에 쓰는(write) 과정이 필요합니다. 

포인터 어려워도 제대로 이해하기 시리즈 1장 컴퓨터 구조의 이해 프로그램 동작6
그림 9. mov r0 #0x8

mov r0 #0x8

메모리의 0x8 번지에 add 연산 결과를 저장하기 위해 r0 레지스터에 0x8을 적습니다.

포인터 어려워도 제대로 이해하기 시리즈 1장 컴퓨터 구조의 이해 프로그램 동작7
그림 10. str r3 [r0]

str r3 [r0]

r0에 있는 값을 주소로 보고 메모리에 r3에 있는 값을 적습니다. 

컴퓨터 구조와 포인터

컴파일러의 최적화 수준과 마이크로아키텍처, ISA에 따라서 다르게 동작하겠지만 컴퓨터가 어떻게 동작하는지 이해하셨을 거라고 믿습니다. 포인터를 이해하기 위해 꼭 필요한 내용이니 어렵더라도 꼭 이해하시길 당부드립니다.

한국어, 영어처럼 자연어에 가까울수록 high level 언어이고 0과 1로 이루어진 기계어와 가까울수록 low level 언어입니다. 앞서 말씀드렸다시피 C언어는 low level 언어입니다. 메모리를 직접 접근할 수 있기 때문인데요. 그 핵심이 포인터입니다. C언어는 다른 언어에 최적화가 필요한 프로그램일수록 임베디드처럼 제한된 리소스를 활용하여 극한의 성능을 끌어내야 할 경우 많이 사용됩니다.

앞의 더하기 프로그램에서 ldr, str 명령어는 메모리에 접근하여 그 값을 레지스터로 불러왔습니다. 포인터는 ldr, str 처럼 메모리에 접근이 필요한 경우 쉽게 구현할 수 있도록 도와줍니다. 그리고 ldr, str과 동작이 유사합니다.

이번 포스팅을 통해 메모리와 주소에 대한 개념을 꼭 이해하시길 바라며 이해가지 않는 내용은 댓글 또는 오픈채팅방에서 질문해 주시면 답변드리겠습니다.

이어서 다음 포스팅에서는 C언어에서 포인터의 개념과 문법에 대해서 살펴보겠습니다.

읽어주셔서 감사합니다.


도움이 되셨다면 즐겨찾기 등록하고 또 오세요


 

C언어의 정석, C언어 재 정립 글 목록보기

함께 읽으면 좋은 글

2021/01/24 - [스터디/C방장] - 2.1 포인터의 본질, 기본 개념부터 [C언어 포인터 어려워도 제대로 이해하기]

 

2.1 포인터의 본질, 기본 개념부터 [C언어 포인터 어려워도 제대로 이해하기]

C언어의 정석, C언어의 재 정립 C언어 독학을 하시는 분들께는 처음부터 올바른 개념을 잡아드리고 C언어의 문법과 사용법, 현업에서 실제로 어떻게 쓰이는지 알고 싶은 분들께 제가 알고 있는 C

sw-daily.tistory.com

2020/12/27 - [스터디/C방장] - 정수형 type 이야기 [C언어 파헤치기 1]

2020/12/27 - [스터디/C방장] - 문자형/character type 이야기 [C언어 파헤치기 2]

2021/01/01 - [스터디/C방장] - floating type 사용 시 주의할 점. single/double precision. 메모리 표현 방식. floating type 이야기 [C언어 파헤치기 3]

2021/01/07 - [스터디/C방장] - 변수의 scope rule, lifetime / extern, static의 올바른 사용 방법


반응형