boostcource
모두를 위한 컴퓨터 과학 (CS50 2019) : David J. Malan
www.boostcourse.org/cs11
실습환경 : CS50 Sandbox & CS50 IDE
C로 작성한 변수들이 실제로 컴퓨터 메모리에 저장되는 모습에 대해서 알아보자.
메모리 주소를 나타내는 방법과 그 주소를 알아내는 방법, 그 주소에 찾아가는 방법을 차례로 보자.
16진수
이전에 2진수에 대해서 공부한 적이 있는데 이번에는 16진수(Hexadecimal)다.
숫자를 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F 총 16개의 기호로 수를 표현하는 방식이다.
10진수에서는 0~9로 숫자를 표현했다면 그 이상 값인
10, 11, 12, 13, 14, 15에 대해서 각각 a, b, c, d, e, f 알파벳으로 표현한다.
16진수를 사용하면 10진수보다 2진수를 간단하게 나타낼 수 있다.
10진수를 16진수로 바꾸기
JPG 이미지 파일은 항상 255 216 255로 시작되는데, 실제 컴퓨터 내에서는 10진수를 사용하지 않는다.
이를 컴퓨터가 이해할 수 있는 2진수로 바꾸어보자,
255 = 2^7 + 2^6 + 2^5 + 2^4 + 2^3 + 2^2 + 2^1 + 2^0 = 11111111(2)
216 = 2^7 + 2^6 + 2^4 + 2^3 = 11011000(2)
255와 216 각각이 8자리(8bits)로 표현된 2진수로 변환됐다.
2^4 가 16이기 때문에 4bits씩 두 덩이로 나누어서 16진수로 표현이 가능하다.
11111111(2) => 1111 / 1111 = f / f => 0xff
11011000(2) => 1101 / 1000 = d / 8 => 0xd8
4bits씩 16진수로 변환 후 0x 를 붙혀 뒤에 오는 문자들이 16진수임을 알려주는 형식이다.
16진수의 유용성
컴퓨터는 2진수로 표현된 내용만을 이해할 수 있다고 배웠다.
예를 들어 ASCII 코드에 의해 "A, B, C"는 10진수로 "65, 66, 67"에 해당하는데
이를 컴퓨터가 이해할 수 있는 2진수로 표현하면 "01000001, 01000010, 01000011" 이다.
하지만 문제는 길이가 너무 길어진다.
마침 컴퓨터는 8개의 bit가 모인 byte 단위로 정보를 표현하는데
위 문제를 극복할 수도 있는 16진수 표현법이 정보를 표현하기에 매우 유용함을 느낄 수 있다.
위의 "A, B, C" 도 16진수를 이용해 "0x41, 0x42, 0x43" 으로 표현할 수 있다.
메모리 주소
정수형 변수 n에 50이라는 값을 저장하고 출력한다고 가정해보자.
n은 int 타입이기에 내 컴퓨터의 메모리 어딘가에 4byte 만큼의 자리를 차지하며 저장되어 있을 것이다.
이 메모리상 주소를 직접 받아볼 수 있는데 C에서는 & 연산자를 사용한다.
그리고 & 연산자로 선언된 주소를 받기 위해서 형식 지정자로 %p 를 사용한다.
#include <stdio.h>
int main(void){
int n = 50;
printf("%p\n", &n); // ? 0x7ffda17cf3fc
}
출력된 0x7ffda17cf3fc 는 변수 n의 메모리상 주소이며 16진법으로 표현되어 있다.
이 주소는 사용자마다, 환경이 바뀔 때 마다 다른 값이 출력된다.
그리고 만일 & 연산자를 붙여 표현한 메모리 주소 앞에 * 사용하면
그 메모리 주소에 있는 실제 값을 얻을 수 있다.
형식 지정자로는 실제 값의 자료형에 맞는 형식 지정자를 사용한다.
#include <stdio.h>
int main(void){
int n = 50;
printf("%i\n", *&n); // ? 50
}
포인터
메모리 주소를 직접 관리하기는 까다롭기에
C에서는 변수의 주소를 쉽게 저장하고 접근할 수 있게 해주는 포인터가 존재한다.
앞서 * 연산자가 어떤 메모리 주소에 있는 값을 받아오게 해주는 것을 확인했는데
이 연산자를 이용해 포인터 역할을 하는 변수를 선언할 수도 있다.
#include <stdio.h>
int main(void){
int n = 50;
int *p = &n; // 포인터 변수 *p에 n의 주소를 저장
printf("%p\n", p); // ? 0x7fff56c399c8
printf("%i\n", *p); // ? 50
}
정수형 변수 n 에는 50이라는 값이 저장되어 있고
포인터 변수 *p 에는 &n 이라는 변수 n의 주소를 저장한다.
int *p = &n 을 해석하자면 * 을 앞에 붙임으로써 이 변수가 포인터다 라고 알려주는 것이고
int를 통해 이 포인터가 int 타입의 변수를 가리킨다는 의미이다.
따라서 첫 번째 printf 문은 포인터 p의 값, 즉 변수 n의 주소를 출력하고
두 번째 printf문은 포인터 p가 가리키는 변수의 값, 즉 변수 n의 값을 출력한다.
// 비교용
int n = 50;
int *p = &n;
printf("%p\n", &n); // ? 0x7fff56c399c8
printf("%p\n", p); // ? 0x7fff56c399c8
printf("%i\n", *&n); // ? 50
printf("%i\n", *p); // ? 50
실제 컴퓨터 메모리에서 변수 p가 저장된 모습은 아래와 같이 표현할 수 있다.
실제 p의 값(n의 주소값)을 생각하지 말고 쉽게 가자면 p가 n을 가리키고 있다 정도로 이해하면 된다.
이 포인터를 기반으로 해서 다양한 데이터 구조를 정의하고 사용할 수 있다.
복습 퀴즈
Q1. 아래 코드를 실행하면 출력되는 결과는?
#include <stdio.h>
int main(void){
int n = 20;
printf("%i \n", *&n);
}
답: 20
&n 은 메모리 주소를 나타내고, *&n은 메모리 주소에 있는 값을 가리킨다.
Q2. 아래와 같이 변수 n과 p를 생성했다. 변수 n의 메모리 주소를 출력하는 코드는?
int n = 5;
int *p = &n;
답: printf("%p \n", p); 또는 printf("%p \n", &n);
Q3. 아래와 같은 코드가 있을 때 출력되는 값은?
int main(void){
x = 5;
func(&x);
printf("%i", x);
}
void func(int *y){
*y = 10;
}
답: 10
x의 메모리 주소인 &x가 함수 func의 매개변수로 전달되는데
때문에 포인터 변수 y는 x의 메모리 주소를 가리키게 된다. (= y는 x를 가리키는 포인터다.)
*y = 10 으로 인해 y가 가리키는 변수인 10이 x의 값으로 변경된다.
'CS > CS50' 카테고리의 다른 글
[CS][CS50] 메모리 - 파일 쓰기 / 파일 읽기 (1) | 2023.06.19 |
---|---|
[CS][CS50] 메모리 - 문자열 / 문자열 비교 / 문자열 복사 / 메모리 할당과 해제 / 메모리 교환, 스택, 힙 (0) | 2023.06.19 |
[CS][CS50] 알고리즘 - 재귀 / 병합 정렬 / 정렬 알고리즘의 실행시간 (0) | 2023.06.18 |
[CS][CS50] 알고리즘 - 버블 정렬 / 선택 정렬 (0) | 2023.06.17 |
[CS][CS50] 알고리즘 - 검색 알고리즘 / 알고리즘 표기법 / 선형 검색 (0) | 2023.06.10 |