📚 시리즈 목차
5. 메모리
5. 메모리
1) 메모리 주소
일단 메모리 주소를 알보기전에 메모리 부터 알아야 한다.
1️⃣ 메모리
메모리는 영어로 당연히 memory라 하고 이는 여러분이 잘 아시는 것 처럼 "기억", "기록"을 뜻하는 영어단어이다.
컴퓨터에서도 마찬가지로 메모리는 기억을 하는 장치로 사용된다.
메모리는 데이터의 저장과 처리를 담당하여 컴퓨터 시스템의 핵심적인 역할을 수행한다.
풀어 말하면, 메모리는 컴퓨터가 실행 중인 프로그램과 작업에 필요한 데이터를 일시적으로 저장하는 장치로 우리가 일상적으로 사용하는 모든 프로그램과 작업에 불가능한 빠른 속도와 신속한 데이터 액세스를 제공한다.
컴퓨터 메모리는 크게 주기억장치와 보조기억장치로 나뉜다.
여기서는 주기억장치에대해서만 다룰 것이다.
주기억장치 (Random Access Memory)
주기억장치는 컴퓨터 시스템에서 실행 중인 프로그램과 작업에 필요한 데이터를 일시적으로 저장하는 주요한 부분입이다.
주로 램(Random Access Memory)이라고 알려져 있으며, 데이터의 빠른 액세스와 처리를 위해 설계되었다.
램은 데이터를 임시로 저장하여 프로그램의 실행 속도와 작업 효율성을 높이는 역할을 수행한다.
Random Access Memory 즉 램은 "랜덤 액세스"라는 특징을 가지고 있어 ,데이터에 빠르게 접근할 수 있다.
이는 주기억장치 내의 어떤 위치든지 직접 접근할 수 있어서 데이터 액세스가 빠르다. 즉, 데이터를 순차적으로 찾을 필요 없이 필요한 데이터에 직접 액세스할 수 있어서 작업 속도가 향상된다.
램은 데이터를 비트 구성된 주소로 구분한다. 각 비트는 데이터의 최소 단위인 "비트(bit)를 나타내며, 비트의 조합으로 다양한 데이터를 표현할 수 있다. 이를 통해 주소를 사용하여 원하는 데이터를 식별하고 액세스 할 수 있다.
주기억장치는 컴퓨터 시스템의 성능에 큰 영향을 미친다 램의 용량이 충분하지 않으면 프로그램이 동작하는 동안 필요한 데이터를 저장하지 못하거나, 작업을 진행하는 동안 데이터를 주고 받는 데 시간이 오래 걸릴 수 있다. 따라서, 적절한 용량과 높은 액세스 속도를 갖춘 주기억장치를 선택하는 것이 중요하다 .
2️⃣ 16진법과 메모리
갑자기 응? 웬 16진법? 할 수도 있다.
컴퓨터에서는 이진법을 가장많이 사용하지 않나? 16진법이라니?
그 이유는 컴퓨터에서 데이터를 처리하기 위해 16진법를 사용할 때 장점이 있기 때문이다.
먼저 16진법에 대해 알아보자
[1] 16진법
16진법은 0부터 15까지의 숫자를 나타내는 기수 체계이다.
숫자를 세는 방법은 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F 이런 식으로 센다.
그럼 '16'은 뭘까?
정답은 '10'이다
why?
이와 같기 때문이다.
우리가 처음 2진법을 배울 때 처럼 16진법도 첫 자리는 16^0, 두 번째 자리는 16^1, 세 번째 자리는 16^2로 점점 나아간다.
그럼 '17'은 16진법에서 '11'이다.
'31'은? '1F' 이다.
'127'은? '7F' 이고
'1024'는? '400' 이다.
잘 보면 10진법보다 16진법을 사용했을 때 큰 숫자를 더 간결하고 표현하기 쉽게 만들어준다.
컴퓨터는 이진법(2진법)을 사용하여 데이터를 처리한다. 이진법은 0과 1만을 사용하여 숫자를 표현하는 체계를 가지고 있다.
그러나 이진법으로 숫자를 표현할 때 많은 비트(bit)가 필요하며, 이는 데이터를 표현하고 저장하는 데에 많은 메모리 공간을 요구한다.
16진법은 이러한 이진법의 단점을 극복하기 위해 사용된다. 16진법은 4개의 이진 숫자(0부터 15까지)를 한 자리로 나타내므로, 이진법보다 훨씬 간결하게 숫자를 표현할 수 있다. 예를 들어, 이진법으로 '10011010'을 표현할 때는 8개의 비트가 필요하지만, 16진법으로는 '9A'로 표현할 수 있다. 이는 숫자를 저장하고 전송하는 데에 필요한 메모리 공간을 많이 감소시켜준다.
메모리와의 관계에서, 컴퓨터 메모리는 주로 바이트 단위로 데이터를 저장한다. 바이트는 8개의 비트로 구성되며, 이는 16진법으로 두 자리로 표현된다. 따라서, 메모리 주소에서 각 바이트는 16진수로 표현되며, 이는 메모리의 특정 위치에 저장된 데이터를 식별하는 데 사용된다.
c언어로 변수가 저장된 메모리 주소를 보려면 아래와 같이 코드를 작성하면 된다.
#include <stdio.h>
int main(void)
{
int n = 50;
printf("%p\n", &n);
}
위 코드를 실행하면 '0x7ffe00b3adbc'와 같은 값을 얻을 수 있고, 이는 변수 n의 16진법으로 표현된 메모리의 주소이다.
(C에서 변수의 메모리상 주소를 받기 위해 '&'이라는 연산자를 사용할 수 있다. 반대로 '*'를 사용하면 그 메모리 주소에 있는 실제 값을 얻을 수 있다. 이에대한 자세한 사항은 다음 단원에서)
2) 포인터
1️⃣ 포인터 연산자 (*) 와 주소 연산자 (&)
먼저 '*'은 C언어에서 포인터 연산자이다. 포인터는 메모리 주소를 저장하는 변수로서, 다른 변수의 메모리 주소를 가리키게 된다.
포인터 변수를 선언할 때 '*'을 사용하여 포인터 타입을 나타내며, 해당 포인터가 가리키는 값을 참조할 때도 '*'을 사용한다.
이렇게만 하면 이해가 가지 않으니 아래 코드를 보면서 이해해보자
#include <stdio.h>
int main(void)
{
int n = 50;
int *p = &n;
printf("%p\n", p);
printf("%i\n", *p);
}
먼저 *p를 보면 포인터 변수 p를 선언하고 &n이라는 값, 즉 변수 n의 주소를 저장했다.
int *p에서 p앞의 *는 이 변수가 포인터라는 의미이고, int는 이 포인터가 int 타입의 변수를 가리킨다는 의미이다.
따라서 첫 번재 printf문과 같이 포인터 p의 값, 즉 변수 n의 주소를 출력하거나, 두 번째 printf문과 같이 포인터 p가 가리키는 변수의 값, 즉 변수 n의 값을 출력할 수도 있다.
쉽게 말하면 n은 일반 변수이고 p는 포인터 변수이다.
따라서 그냥 p자체는 변수가 저장된 메모리주소 값이고 *p는 포인터가 가리키는 값을 참조한다.
여기서 포인터가 가리키는 값 즉 메모리 주소 값은 p가 가지고있다.
분명 이해가 가지 않았을 것이다. 걱정할꺼없다. 나도 그랬다.
계속 보고 또 보고 또보면 된다.
또 다른 예를 통해서 이해해보자.
#include <stdio.h>
int main(void)
{
int x = 42; // 1. 정수 변수 x를 선언하고 값 42를 할당
int *p = &x; //2. 포인터 변수 p를 선언하고 x의 주소를 할당
printf("%i\n", *p); // 3. 포인터 변수 p를 역참조하여 x의 값을 출력
*p = 14; // 4. 포인터 변수 p를 역참조하여 x의 값을 수정
printf("%i\n", x); // 5. x의 값을 출력하여 변경된 값을 확인
}
1번은 이해가 갔을 것이다.
2번 포인터 변수에는 바로 값을 할당할 수 없다. 반드시 초기화 과정이 있어야한다. 즉 포인터가 어딘가를 가리켜야 한다.
그래서 변수 x의 주소를 가리키도록 2번에서 설정해줬다.
3번은 포인터 변수가 가리키는 값을 출력하는 것이다. 여기서 만약 '*'를 뺀다면 주소가 출력될 것이다. (앞에 %i 도 %p로 바꿔야한다.)
4번은 포인터 변수 p를 역참조하여 x의 값을 수정해주고 있다. 쉽게말해 현재 포인터 변수 p는 어떤 값을 가지고 있는게 아니라 x의 값이있는 메모리 주소를 가리키고있다. 그런데 *p를 하면 p에 저장되어있는 x값의 메모리 주소가 저장되어있는 p를 참고해서 x의 값을 가져오는데 거기에 14를 대입했으니 x의 값이 수정되는 것이다.
3) 문자열
1️⃣ string
우리는 지금까지 문자열을 저장하기 위해 CS50 라이브러리에 포함된 string 자료형을 사용하였다.
아래처럼 말이다.
string s = “EMMA”;
문자열은 결국 문자의 배열이고, s[0], s[1], s[2], --- 와 같이 하나의 문자가 배열의 한 부분을 나타낸다.
그리고 가장 마지막의 \0은 0으로 이루어진 바이트로, 문자열의 끝을 표시하는 약속이다.
여기서 변수 s는 결국 이러한 문자열을 가리키는 포인터가 된다.
더 상세히는 문자열의 가장 첫번째 문자, 즉 주소 0x123에 있는 s[0]를 가리키게 된다.
실제 CS50 라이브러리를 보면 string 자료형은 아래와 같이 정의되어있다.
typedef char *string
여기서 typedef는 새로운 자료형을, char *은 문자에 대한 포인터를, string은 자료형의 이름을 의미합니다.
다시말해 에초에 C언어에는 string이라는 타입이 없다.
그렇다면 사실 문자열은 아래처럼 표현했어야했다.
#include <stdio.h>
int main(void)
{
char *s = "EMMA";
printf("%s\n", s);
}
char*s에서 s라는 변수는 문자에 대한 포인터가 되고, "EMMA"라는 문자열의 가장 첫 번째 값을 저장하게된다.
정리해보자면 C언어에서 문자열은 문자의 배열로 표현된다. 일반적으로 'char'타입의 배열로 문자열을 표현하고 문자열의 끝을 나타내기 위해 '\0' (널 문자)를 사용한다.
그런데 여기서 분명 이해가 되지 않을 것이다.
"왜 printf "%s\n", *s) 을 하지 않지 ?", "s는 포인터가 가리키는 메모리 주소값일텐데?"
그 이유는 바로 문자열 형식 지정자 때문이다.
printf("%s\n", s);에서 %s는 문자열 형식 지정자이다. %s는 문자열의 시작 주소를 받아 해당 주소에서부터 널 문자('\0')를 만날 때까지 문자들을 출력한다. 따라서 printf("%s\n", s);는 s가 가리키는 문자열의 전체 내용을 출력하게된다.
4) 문자열 비교
#include <cs50.h>
#include <stdio.h>
int main(void)
{
// 사용자로부터 s와 t 두 개의 문자열 입력받아 저장
string s = get_string("s: ");
string t = get_string("t: ");
// 두 문자열을 비교 (각 문자들을 비교)
if (s == t)
{
printf("Same\n");
}
else
{
printf("Different\n");
}
}
두 문자를 비교하기위에 위 코드처럼 작성한뒤 비교하면 제대로 동작할까?
정답은 'X'다
변수 s는 처음으로 작성한 문자열을 입력받고 해당 문자열의 첫 번째 문자 주소를 저장하고 변수 t는 두 번째로 작성한 문자열을 입력받고 해당 문자열의 첫 번째 문자 주소를 저장하기에 서로 비교할 시 저장되는 주소가다르기에 같은 문자열을 입력하더라도 다르다고 나오는 것이다. 한 마디로 변수 s와 t는 값이아니라 주소값을 저장하기에 주소값이 서로 같냐고 물어보면 당연히 틀리다고 하는것이다.
따라서 문자열을 올바르게 비교하기 위해서는 string라이브러리에 있는 strcmp()함수를 사용하거나 아래 코드 처럼 하면 된다.
#include <stdio.h>
#include <cs50.h>
#include <string.h>
int main(void)
{
char *s = get_string("string1:");
char *j = get_string("string2:");
// 먼저 문자열 길이가 다르면 early return
printf("%s\n", *s);
if(strlen(s) != strlen(j))
{
printf("different\n");
return 0;
}
// 문자열 길이가 같다면
for(int i=0, n = strlen(s);i<n;i++)
{
// 각 캐릭터 비교
if(s[i] == j[i])
{
// 캐릭터가 같다면 통과
continue;
}
// 캐릭터가 서로다른게 존재하면 "다르다"출력하고 함수 종료
printf("different\n");
return 0;
}
// 위 모든 과정을 통과하면 결국은 같은 문자열이다.
printf("same\n");
}
5) 메모리 할당과 해제
malloc 함수를 이용하여 메모리를 할당한 후에는 free라는 함수를 이용하여 메모리를 해체해줘야 한다.
그렇지 않은 경우 메모리에 저장한 값은 쓰레기 값으로 남게 되어 메모리 용량의 낭비가 발생하기 때문이다.
이러한 현상을 '메모리 누수'라고 일컫는다.
valgrind 라는 프로그램을 사용하면 우리가 작성한 코드에서 메모리 관련된 문제가 있는지 쉽게 확인할 수 있다.
#include <stdlib.h>
void f(void)
{
int *x = malloc(10 * sizeof(int));
x[10] = 0;
}
int main(void)
{
f();
return 0;
}
위 코드를 보면 malloc를 통해 포인터 x에 int형의 사이즈(4바이트)에 10배에 해당하는 크키의 메모리 즉, 40 바이트를 할당했다.
그리고 x의 10번째 값으로 0을 할당한다.
이 코드를 valgrind로 검사해보면 버퍼 오버플로우와 메모리 누수 두 가지 에러를 확인 할 수 있다.
먼저 버퍼 오버플로우는 x[10] = 0; 코드로 인해 발생한다.
📌 버퍼
버퍼(Buffer)는 일시적으로 데이터를 저장하는 메모리 영역을 말한다. 프로그램에서 데이터를 처리할 때 임시로 사용되는 작은 메모리 공간으로, 데이터를 한곳에서 다른 곳으로 전송하는 동안 데이터를 보관하고 일시적으로 저장하는 역할을 한다.
📌 버퍼 오버플로우
버퍼 오버플로우는 프로그램이 메모리 버퍼를 초과하여 데이터를 저장하거나 수정할 때 발생하는 보안 취약점이다.
프로그램이 메모리에서 일정한 크기의 버퍼를 할당하고 데이터를 저장할 때, 버퍼에 저장되는 데이터의 크기가 버퍼의 크기를 초과하는 경우 버퍼 오버플로우가 발생한다. 이러한 상황에서는 초과된 데이터가 다른 메모리 영역을 침범하거나 약용될 수 있으며, 악의적인 공격자가 해당 취약점을 이용하여 시스템에 해를 입힐 수 있다.
버퍼 오버 플로우는 주로 입력 데이터의 길이를 체크하지 않거나, 잘못된 크기의 메모리 버퍼를 사용하는 등의 프로그래밍 실수로 인해 발생한다.
우리는 10개의 int형의 배열을 만들었는데 배열의 인덱스가 0부터 시작한다는 점을 감안하면 인덱스 10은 11번째 인덱스에 접근하겠다는 의미이고, 이는 정의되지 않은 것이기 때문에 버퍼 오버플로우가 발생한다.
따라서 이 오류는 0에서 9사이의 인덱스를 사용하면 해결할 수 있다.
메모리 누수는 x라는 포인터를 통해 할당한 메모리를 해체하기 위해 free(x)라는 코드를 추가해줌으로써 해결할 수 있다.
📌 free()
free() 함수는 mallock()를 통해 할당된 메모리를 해제하는데 사용된다. 즉 해당 메모리 블록을 해제하고, 해당 메모리 공간을 다시 사용가능한 상태로 만든다.
동적으로 할당된 메모리는 사용기 끝나면 명시적으로 해제해야 한다
아래처럼 고치면 모든 에러가 해결된다.
#include <stdlib.h>
void f(void)
{
int *x = malloc(10 * sizeof(int));
x[9] = 0;
free(x)
}
int main(void)
{
f();
return 0;
}
6) ⭐️ 메모리 교환, 스택, 힙
#include <stdio.h>
void swap(int a, int b);
int main(void)
{
int x = 1;
int y = 2;
printf("x is %i, y is %i\n", x, y);
swap(x, y);
printf("x is %i, y is %i\n", x, y);
}
void swap(int a, int b)
{
int tmp = a;
a = b;
b = tmp;
}
여기서 swap 함수를 보면 정수 a와 b를 입력받아 그 값을 서로 바꾸는 일을 수행한다는 것을 알 수 있다.
과연 의도대로 잘 동작 할까?
정답은 'X'다 .
why?
x와 y는 바뀌지 않는다. 왜냐하면 swap 함수에게 x,y를 넘겨주는 순간 해당 값자체를 넘겨주는게 아니라 복사본을 넘겨준다. 따라서
swap 함수에서 처음에 넘겨받은 x,y는 값이 복사되어 a,b로 전달되어지고 그 안에서 어떻게 바꾸는 외부 값에는 영향이 없다는 것이다.
즉, 서로 다른 메모리 주소에 저장되기 때문에 변경이 되지 않는다는 것이다.
메모리리 적으로 이야기해보자.
위 그림과 같이 메모리 안에는 데이터가 저장되는 구역이 나뉘어져 있다.
- 머신 코드 영역에는 우리 프로그램이 실행될 때 그 프로그램이 컴파일된 바이너리가 저장된다.
- 글로벌 영역에는 프로그램 안에서 저장된 전역 변수가 저장된다.
- 힙 영역에는 malloc로 할당된 메모리의 데이터가 저장된다.
- 스택에는 프로그램 내의 함수와 관련된 것들이 저장된다.
이를 바탕으로 생각해보면 이전 코드에서 a,b,x,y, tmp 모두 스택 영역에 저장되지만 a와 x, b와 y는 그 안에서도 서로 다른 위치에 저장된 변수이다.
why?
main이 먼저 호출되어 스택에 쌓인다. 그리고 main이 swap을 호출하여 그 위에 쌓인다.
swap 함수에는 a,b가 있고 또 다른 변수인 tmp가 있다. 이들은 swap 함수 메모리 프레임안에서 존재하는 것이다.
따라서 main 함수 메모리 프레임 안에 있는 x,y는 영향을 받지 않는다.
이러한 이유 때문에 a와 b를 바꾸는 것이 x와 y를 바꾸는 것에 아무런 영향도 미치지 못하는 것이다.
이와 같은 문제를 해결하는 방법은 복사본을 넘겨주는게 아니라 참조에 의한 전달을 사용하는 것이다.
즉, 포인터를 활용해면 된다.
#include <stdio.h>
void swap(int *a, int *b);
int main(void)
{
int x = 1;
int y = 2;
printf("x is %i, y is %i\n", x, y);
swap(&x, &y);
printf("x is %i, y is %i\n", x, y);
}
void swap(int *a, int *b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
7) 파일 쓰기
- scanf
- fopen
- fprintf
- fclose
힙 영역에서는 malloc에 의해 메모리가 더 할당될수록, 점점 사용하는 메모리의 범위가 아래로 늘어난다.
마찬가지로 스택 영역에서도 함수가 더 많이 호출될수록 사용하는 메모리의 범위가 점점 위로 늘어난다.
이렇게 점점 늘어나다 보면 제한된 메모리 용량 하에서는 기존의 값을 침범하는 상황도 발생할 것이다.
이를 힙 오버플로우 또는 스택 오버플로우라고 일컫는다.
get_string 코드
#include <stdio.h>
int main(void)
{
char s[5];
printf("s: ");
scanf("%s", s);
printf("s: %s\n", s);
}
scanf라는 함수는 사용자로부터 형식 지정자에 해당되는 값을 입력받아 저장하는 함수이다.
get_string 코드에서는 scanf에 그대로 s를 입력해주었다. 즉 s& 아니다
그 이유는 s를 크기가 5인 문자열, 즉 크기가 5인 char 자료형의 배열로 정의하였기 때문이다.
파일 쓰기
#include <cs50.h>
#include <stdio.h>
#include <string.h>
int main(void)
{
FILE *file = fopen("phonebook.csv", "a");
char *name = get_string("Name: ");
char *number = get_string("Number: ");
fprintf(file, "%s,%s\n", name, number);
fclose(file);
}
fopen : 파일을 FILE이라는 자료형으로 불러올 수 있다. (첫 번째 인자는 파일의 이름, 두번째 인자는 모드로 r은 읽기, w는 쓰기, a는 덧붙이기를 의미)
fclose : 작업이 끝난 후에는 fclose 함수로 파일에 대한 작업을 종료해줘야 한다.
이와 같은 방식으로 get_long, get_float, get_char도 비슷한 방식으로 직접 구현할 수 있다.
1️⃣ get_long()
#include <stdio.h>
int main(void)
{
long x;
printf("long : ");
scanf("%ld", &x);
printf("ld: %ld", x) ;
}
2️⃣ get_float()
#include <stdio.h>
int main(void)
{
float x;
printf("float : ");
scanf("%f", &x);
printf("float: %.2f", x) ;
}
3️⃣ get_char()
#include <stdio.h>
int main(void)
{
char x;
printf("char : ");
scanf("%c", &x);
printf("char : %c", x) ;
}
퀴즈 복습
1번 문제
printf("%i \n", *&n); 은 printf( "%i \n", n) 과 같다.
첫 번째 printf는 n의 주소를 얻은 후 역참조하여 값을 출력하는 것이고 두 번째 printf는 단순히 n의 값을 직접 출력하는 것이다.
일반적으로 첫 번째 방법은 추천하지 않는다.
2번 문제
int *p = &n; 는 포인터 변수 p 를 선언하고, 변수 n의 주소를 p에 할당한다는 뜻이다.
%p 는 주소를 출력하기 위한 서식 지정자이다.
1번 선택지 *n은 n의 주소값을 역참조한다는 뜻이다. n은 포인터 변수가 아니고 값 자체가 저장되어있다.
3번 선택지는 n값을 출력하려면 printf(%d \n", n); 이렇게 해야한다.
4번 선택지에서 printf("%p\n", &p)는 포인터 변수 p 자체의 주소를 출력하고 printf("%p\n", p); 는 변수 n의 주소를 출력한다. 즉 서로 다른 주소 값을 출력한다.
4번 문제
1번 선택지는 "D"가 출력된다.
3번 선택지는 "I"가 출력된다.
4번 선택지는 "T"가 출력된다.
printf("%c\n", *s)는 "E"가 출력된다.
printf("%s\n", s)는 "EDWITH"가 출력된다.
5번 문제
문자 하나당 1바이트가 필요한데 "EDWITH"는 6글자라 6바이트지만 종단문자(\O)까지 포함되기에 7바이트가 피룡하다.
6번 문제
- malloc() 는 동적 메모리 할당을 위해 사용되는 함수이다.
- free() 함수를 호출하면 malloc 함수로 할당한 메모리가 해체되어 다른 용도로 재사용될 수 있다. 메모리를 할당하고 사용한 후에는 메모리 누수를 방지하기 위해 반드시 free 함수를 호출해야 한다.
- unmemory()와 mfree()는 C언어에 내장된 표준 함수가 아니다.
7번 문제
- 스택(stack)영역 : 함수 호출과 관련된 지역 변수 및 함수의 임시 데이터를 저장하는 데 사용된다. 함수가 호출될 때마다 해당 함수의 지역 변수와 함수의 실행 컨텍스트가 스택에 할당되며, 함수가 반환될 때 해당 데이터는 스택에서 제거된다. 스택은 후입선출 구조로 동작한다.
- 힙(Heap)영역 : 힙 영역은 동적으로 할당된 메모리를 저장하는 데 사용된다. 쉽게말해 프로그램 실행 중에 사용자에 의해 메모리를 할당하고 해체할 수 있는 영역이다.
- 글로벌(Global)영역: 글로벌 영역은 전역 변수와 정적 변수를 저장하는 데 사용된다. 전역 변수는 프로그램 전체에서 접근 가능한 변수로, 프로그램이 시작될 때 생성되며 종료될 때까지 유지된다. 정적 변수는 특정 함수나 블록 내에서만 접근 가능한 변수로, 프로그램이 실행될 때 생성되고 종료될 때 까지만 유지된다.
- 머신 코드(Machine Code)영역 : 머신 코드 영역은 프로그램의 실행 코드를 저장하는 데 사용된다. 컴파일러나 인터프리터에 의해 생성된 기계어 명령어들이 이 영역에 위치하며, CPU가 순차적으로 이 명령어를 실행하여 프로그램을 실행한다. 이 영역은 읽기 전용이며, 프로그램 실행 중 수정되지 않는다.
8번 문제
- fopen() : 파일 열기 함수
- fprintf() : 형식화된 데이터 파일을 출력하는 함수
- fclose() : 파일 닫기 함수
9번 문제
x값을 전달하면 복사되어 전달되기에 바뀌지 않았겠지만 주소값을 전달해서 참조에 의한 전달이 된다. 따라서 값이 변경된다.
위 코드를 조금 더 정확하게 표현하면 아래와 같다.
#include <stdio.h>
void func(int *y);
int main(void)
{
int x = 5;
func(&x);
printf("%i\n", x);
}
void func(int *y)
{
*y = 10;
}
10번 문제
x,y 값을 그대로 주면 복사해서 주는 것이기에 기존 변수와의 연결이 끊긴다.
따라서 x,y의 주소를 줘서 참조에 의한 절달을 해야한다.
*x, *y는 x,y가 포인터 변수가 아니라 x,y는 주소값이 아니다. 따라서 역참조를 할 수 없다.
Reference
'CS > CS50' 카테고리의 다른 글
[CS50] 1. 컴퓨팅 사고 (1) | 2023.12.31 |
---|---|
[CS50] 6. 자료구조 (0) | 2023.06.17 |
[CS50] 4. 알고리즘 (0) | 2023.05.31 |
[CS50] 3. 배열 (0) | 2023.05.27 |
[CS50] 2. C언어 (0) | 2023.05.25 |