본문 바로가기
Computer Science

컴퓨터는 데이터를 어떻게 사용할까 1: 메모리, OS

by lucid_07 2023. 10. 13.
반응형
C 언어를 기준으로 설명. 가상메모리, 자료구조와 포인터, 캐시와 가상메모리에 대한 문제는 다른 페이지에서 상세하게 다루고, 여기에서는 전체적인 흐름을 위한 간단하고 단순화한 설명만 있음.

 

하드웨어: 메모리

 

RAM

컴퓨터의 기억장치는 휘발성 메모리와 비휘발성 메모리가 있다. 휘발성 메모리로 대표적인 것은 RAM(Random Access Memory) 비휘발성 메모리로 대표되는 것은 ROM(Read Only Memory)등이 있다. 보통 메모리라 함은 우리가 컴퓨터에서 주로 작업대로 사용하는 RAM을 지칭하는 것으로 사용한다.
RAM은 데이터를 순차적으로 접근해야 하는 ROM과는 달리 랜덤으로 접근할 수 있기 때문에 속도가 매우 빠르다. 이런 매우 빠른 특성 때문에 느린 ROM과 빠른 CPU를 중개(👉 캐시Cache)하는 역할을 한다. 전원이 꺼지면 모두 사라지기 때문에 필요한 데이터들은 틈틈히 ROM에 저장하여 관리한다.
 

Bit와 Byte

1 바이트는 8 비트로 이루어져있다.

한 개의 메모리 소자는 0 혹은 1의 값을 보관할 수 있다-즉 메모리 저장 최소 단위는 비트이다-. 이진수 한자리를 가리켜 bit 라고 한다. 8 bit는 묶어서 1 byte라고 표현한다. 8 byte로 표현되는 숫자는 2^8 = 256개의 숫자이다.
 

워드(Word)

CPU의 레지스터(register)의 메모리 공간은 CPU가 연산을 하기위한 데이터를 불러놓는 작은 메모리 공간이다. 레지스터의 크기는 컴퓨터 상에서 연산이 실행되는 최소 단위이고, 이를 워드라고 한다(즉, CPU가 한 번에 처리할 수 있는 데이터 크기의 단위이다). 32 bit 컴퓨터는 1 word가 32 bit, 즉 4 byte였지만, 현재의 64 bit 컴퓨터의 경우 1 word가 64 bit, 즉 8 byte가 된다.
 

OS의 메모리 사용

운영체제가 관리하는 가상메모리의 구조

컴퓨터를 총괄하는 시스템인 운영체제는 프로세스마다 가상메모리를 구성하여 응용프로그램이 사용할 수 있도록 제공한다. 응용 프로그램이 데이터를 효율적으로/안전하게 사용하도록 가상화Virtualize(👉 운영체제/가상메모리: 가상 메모리는 실제 물리 메모리와 다른 주소를 가지며, 가상화하면 저장 공간을 한 공간에 있는 것처럼  묶어서 관리하게 된다)하여 데이터를 관리하는 것이다. 아래 그림은 제일 위가 주소가 크며 아래로 갈수록 0에 가까운 주소를 가진다(스택은 거꾸로 자란다).

메모리의 최소 단위는 1 bit이지만, 프로그래밍 언어에서 자료형의 최소 단위는 1 byte이므로, (C의 경우 char 자료형의) 배열이나 포인터를 통해 1byte 단위로 메모리에 접근할 수 있다. 하지만 최소 단위가 1 byte인 것과 별개로 비트 연산을 통해 비트 단위의 연산으로 원하는 정보를 담을 수도 있다(👉 C언어 비트 연산 활용).

참고로 데이터형태는 언어마다 필요에 따라 다르게 설계되어 있다:

C에서 char형태는 1바이트이며, ASCII문자를 저장하는 데 사용된다. 그러나 C 표준은 어떤 문자를 사용하는지 명시하지 않으며, 유니코드를 사용하기 위해서는 추가적인 라이브러리가 필요할 수 있다. unsigned char은 양수만을 지원한다. char는 첫째 자리는 보수를 위해 음수인지 양수인지 판단하는 데에 사용하지만 unsigned char는 0부터 255까지 숫자를 나타낼 수 있다(-> 2의 보수 참고). 또한 문자열을 전달할 때에는 첫번째 글자의 포인터를 전달하여 \n이 나타날 때까지 읽도록 하는 방식으로 처리한다.
Java에서 char 데이터는 2바이트이며, 유니코드 문자를 직접적으로 저장하여 직접적으로 유니코드를 지원한다. 또한 문자열의 집합인 string 데이터형을 지원하여 여러 문자를 한꺼번에 처리할 수 있는 메서드들(equals())도 다양하게 지원한다.

여기에서 보이는 중요한 차이점을 확인할 수 있는데,
절차 지향 언어인 C 언어는 순차적으로 메모리 주소에 접근하여 char를 하나씩 읽어나가는 반면에, 객체 지향 언어인 Java는 설계적 측면이 강조되어, 객체간 상호작용을 중심으로 코드가 진행되도록 메서드들을 지원한다는 것을 확인할 수 있다.

참고 자료: [C언어 입문] 1-1장. 문자형, 정수형, 실수형 변수 이해하기 : 네이버 블로그 (naver.com)

 

변수

변수는 프로그램에서 사용할 데이터를 저장하기 위한 메모리 공간을 의미한다: 메모리 공간에 이름을 붙여 사용하기 편리하게 정의하는 것이 변수 선언의 목적.

변수를 선언declare함으로써 자료형에 따라 사용할 메모리의 크기를 컴파일러/인터프리터에게 알려주게 된다. 변수의 초기화initialize는 해당 메모리 공간에 쓰레기 값 대신에 넣을 값을 일러줌으로써 의도하지 않은 데이터를 사용하지 않도록 해 준다.

초기화 하지 않고 변수를 사용할 수 있지만 대부분의 프로그램은 자동으로 초기화하지 않으므로 사용에 주의해야 한다.

 

메모리 할당과 Stack, Heap & Static

메모리 사용을 위해 메모리 공간을 할당해야 하는데, 할당/해제의 시간에 따라 동적할당/정적할당으로 나눌 수 있다.

출처: https://myblog.opendocs.co.kr/archives/1301

 

동적 할당

실행시간(런타임)에 메모리를 할당하며 데이터 구조의 크기가 알려져 있지 않아 유연성과 효율성이 중요한 경우 사용된다.

Stack

  • 지역 변수와 파라미터: 함수 호출(Function call)이 발생할 때마다 자동으로 할당하고 반환.
  • 컴파일러에 의해 미리 정의되어 있는 루틴에 의해서 수행되며, 프로그래머는 이에 대해 신경 쓸 필요가 없다. 이를 temporal memory allocation이라고 하며, 함수 실행이 끝나면 바로 메모리가 flush된다.
  • 자료구조인 스택과는 관련 없이 스택 자료 형태로 관리되므로 흔히 스택이라고 부르는 것이다.

Heap

  • 동적 할당된 메모리, 클로저(코드 영역 주소 저장), 객체(객체에 대한 참조): 객체를 선언하면 항상 힙 메모리에 영역이 생성되며, 객체에 대한 참조 정보는 스택 메모리에 저장된다.
  • brk 혹은 sbrk 시스템 호출을 통해 메모리 크기를 조정할 수 있는 malloc, calloc, realloc 그리고 free함수를 통해 관리한다: 프로그래머가 관리.
  • 힙 자료구조와 관련 없이 힙 형태로 관리되므로 흔히 힙이라고 부르는 것이다.
  • 힙 메모리의 데이터는 모든 스레드에서 공유하기 때문에 스택만큼 안전하지 않다. 또한 프로그래머가 제대로 관리하지 못하면 메모리 누수(Memory Leak)가 발생한다.
  • 또한 Memory Fragmentation메모리 단편화와 Cache Miss캐시 누락(메모리 전체에 분산되는 특성으로 인해)이 주요 이슈이다.

 

정적 할당

컴파일러에 의해 선언된 변수에 대해 할당된다. 컴파일동안 할당되며, 전체 실행 동안 할당되고 유지된다. 따라서 데이터 구조의 크기가 고정되어 있고, 최적화하여 사용할 필요가 있다.

Static

프로그램 전체 실행 시간 동안 할당되고 유지된다. 프로그램에서 특정 전역 변수를 얼마나 많이 호출하던지간에 항상 같은 메모리 주소의 위치를 참조한다.

  • Data
    • 초기화된 전역 변수와 static 변수는 initialized data 영역에 위치한다.
    • 초기화 되지 않은 전역변수와 static 변수는 unintialized data 영역에 위치한다.
    • 컴파일 단계에서 검사를 마치고 메모리 할당이 이루어진다.
  • Code
    • const(read only)로 선언된 변수들은 code 영역에 위치한다.
    • 코드의 내용은 code 영역에 위치한다.

 

동적할당: 런타임 시에 할당된다.
- Stack: 함수가 호출될 때마다 할당되는 지역 변수들이 저장되었다가 함수가 종료되면 해제된다. 스레드 간 공유되지 않는다.
- Heap: 프로그래머가 필요할 때마다 코드로 메모리를 할당할 경우 할당과 해제 모두를 해 주어야 메모리 누수가 생기지 않는다. 스레드 간에 데이터가 공유된다.

정적할당: 컴파일 시간에 할당된다.
- Static: 프로그램이 실행되어 종료될 때까지 저장되는 전역변수가 저장된다.

 

 

배열과 포인터

가상화된 저장 장치에 저장된 데이터는 다양한 형태의 자료구조로 저장된다. 자료구조는 효율적인 접근 및 수정을 가능하게 하는 자료의 조직, 관리, 저장을 의미한다. 그 중에서 배열과 포인터는 메모리에 대한 단순한 접근을 할 수 있는 자료구조와 자료형이므로 알아 둘 필요가 있다.

👉 데이터는 OS에 의해 메모리에(실행 순서에 따라) 차곡차곡 저장되지만(가상화된 환상이지만), 프로그래밍 코드에 의해 자료구조는 하나의 객체로 선언되어 일정한 메모리 공간을 차지하며 컴파일러/인터프리터가 자료구조의 특성대로 데이터를 다루게 된다.

배열: 같은 타입의 변수들로 이루어진 유한 집합

from. 모코님 블로그

인덱스에 대응하는 데이터로 이루어진 자료구조이다. 같은 종류의 데이터가 순차적으로 저장되어, 저장된 값의 인덱스가 배열 시작점으로부터 상대적인 위치가 된다(0 부터 시작). 지정된 데이터 형태의 크기를 하나의 컨테이너로 저장한다. 동일한 자료형의 데이터를 연속 공간에 저장하기 위한 자료구조이다. 한 번 선언하면 크기를 바꿀 수 없다.
 

포인터: 변수의 메모리 공간 주소를 가르키는 변수

int *pa;
int a[10];

pa = a;

메모리 공간에 선언된 int형 a 배열(10 개 index 지님)과 첫번째 요소를 가르키는 포인터변수 pa, 16 진수 숫자는 가상 메모리의 주소를 나타낸다.

C언어에 존재하는 포인터 변수는 참조하는 형태로 변수로 선언된 메모리 주소에 접근할 수 있다(C 언어로 low level의 메모리 접근을 가능하게 해 준다). 특히 포인터는 포인터 연산이 가능해 직접적으로 가상메모리에 접근하여 데이터를 조작하고 읽어들이거나 전달하는데 사용할 수 있다.

위에서 언급했듯이, 객체에 대한 참조 정보(포인터)는 stack에 저장되고, 객체는 heap에 할당된다.

 

배열과 포인터의 비교

  1. 배열 변수 선언을 하게되면 함께 데이터를 저장할 공간을 (선언한 자료형)X(배열의 크기) 만큼 연속적인 공간을 할당한다. 반면 포인터는 자신이 데이터를 저장할 위치를 가지지 않고, 데이터를 저장할 공간의 주소를 저장한다(그 메모리 공간을 가르킨다).
  2. 배열의 이름은 첫 번째 원소의 메모리 주소를 가르킨다. 포인터의 경우 할당된 메모리의 주소를 가르킨다.
  3. 포인터는 변수이므로 증감 연산자를 사용하여 다른 주소를 가르킬 수 있지만 배열은 상수로 간주하여 연산자를 사용할 수 없다.
  4. 포인터는 변수, 배열은 상수라고 생각하면 편하다.

 

+) 함수의 변수 전달 방법

Call by value

인수(argument)의 값을 복사해 와, 그 값을 매개변수(parameter)에 할당한다 → 원본 값 변경해도 변경 안됨. 읽기 전용 접근(read-only access)라고 표현한다.

call by reference

인수로 메모리 주소를 매개변수에 전달. 참조reference를 전달한다. 참조란 변수의 이름을 가리킨다. → 원본 값 변경하면 같이 변경됨

call by pointer **

인수로 메모리 주소를 매개변수에 전달. 포인터pointer를 전달한다. 포인터는 변수의 메모리 주소를 저장하는 변수이다.

세 방식의 비교

  1. Call by Value
    • 전달해야 하는 값이 크다면 작업이 무거워질 수 있음.
    • 객체지향 프로그래밍에서는 객체 크기가 커질수록 값으로 전달 메커니즘을 사용하지 않음
    • 값으로 전달 메커니즘은 간단하고 읽기 전용으로 쓰기 좋다.
  2. Call by Reference
    • 호출되는 함수 쪽에서 매개변수를 변경하여 원본(argument)를 변경할 수 있음. 복사가 필요하지 않다.
  3. Call by Pointer
    • 참조로 전달 메커니즘과 같은 장점을 지님, C++에서는 잘 사용하지 않지만, 전달해야 하는 자료가 포인터 특성(C언어 문자열, 배열 등)을 갖고 있는 경우 포인터 전달 메커니즘 사용

함수에 전달할 때 뿐만 아니라 리턴값을 받을 때에도 세가지 방식으로 인수를 전달받을 수 있다.

 

출처

https://myblog.opendocs.co.kr/archives/1301

[OS] 프로세스 메모리 구조 (코드, 데이터, 스텍, 힙) (tistory.com)

스택, 힙, 메모리의 차이점과 메모리 할당

 

반응형