// 날짜 대신 Unit 명을 써주고 문제들은 맨 아래에 모아서 풀이를 적어봤습니다.
-keyword-
포인터, 역참조, 메모리 할당, malloc, free, 스택, 힙, memset
Unit 34. 포인터 사용하기
-포인터(pointer)-
포인터란 c언어에서 메모리 주소값을 저장하는 변수다. 포인터 변수라고도 부른다.(참고 : tcpschool)
int a = 1;
위는 간단하게 1로 초기화되는 int형 변수 a를 선언한 코드다. 사실 변수를 선언했을 때 마법처럼 짠하고 변수가 생성되는게 아닌 컴퓨터의 메모리에 일정 공간을 확보해서 그 공간에 변수의 값을 저장하고 가져오는 것이다.
이 메모리에서 해당 변수의 주소(위치)를 메모리 주소라고한다.
즉 위 int a = 1;이라는 코드가 실행될 때 컴퓨터의 메모리의 특정 공간을 할당 받고 그 공간에 1이라는 값이 저장되는 것이다. 변수 a의 메모리 주소는 &를 붙여서 쉽게 구할 수 있다.
참고로 운영체제의 비트 수에 따라 메모리 주소의 범위가 달라지는데 32비트인 경우 0x00000000 ~ 0xFFFFFFFF, 64비트의 경우 0x0000000000000000 ~ 0xFFFFFFFFFFFFFFFF이다. 실제로 32bit 파일, 64bit 파일을 디버거로 열어보면 주소의 범위가 다른 것을 알 수 있다. 따라서 크기를 구해주는 sizeof로 뒤에 정리한 포인터 변수를 32비트, 64비트 환경에서 확인해보면 int형 변수의 경우 32비트에서는 4바이트, 64비트에서는 8바이트로 달라지는 것을 볼 수 있다.(저장할 주소의 범위가 다르기 때문)
-&
&은 비트 단위 and 연산으로 쓰였던 연산자지만, 변수 앞에 &을 붙여준다면 해당 변수의 메모리 주소를 구해준다.
&<변수명>
#include <stdio.h>
int main()
{
int a = 1;
printf("%p", &a);
}
위는 1이 저장되는 int형 변수 a를 선언하고 &으로 a의 메모리 주소 값을 출력하는 코드다. 메모리 주소는 pointer의 약자인 %p 서식 지정자를 사용해서 출력할 수 있다.
실행 결과를 보면 a의 메모리 주소가 16진수 형태로 출력된 것을 볼 수 있다. 아마 메모리 상의 006FFD18 위치에 값 1이 저장돼 있을 것이다.
#include <stdio.h>
int main()
{
int a = 1;
printf("%x\n", &a);
printf("%X\n", &a);
printf("%p\n", &a);
}
메모리 주소가 16진수 형태라 혹시하고 16진수 서식 지정자인 %x, %X를 써서 출력해봤다. 위 결과처럼 잘 출력되지만 개인적인 생각으로는 %p의 결과가 좀 더 보기 좋은 것 같다.
scanf("%d", &a);
번외로 추측해보면 앞서 배운 scanf 함수는 이상하게 &<변수>로 입력 값을 변수에 넣어줬다. &를 써준 것으로 보아 a = <입력 값> 이렇게 값을 넣어주는게 아닌 a의 메모리 주소에 접근해 해당 공간에 값을 넣어주는게 아닐까 생각이든다.
-포인터 변수 선언
메모리 주소를 저장하는 포인터 변수는 자료형 뒤에 *를 붙여서 선언한다.
<자료형> *<변수> = &<변수>;
위처럼 자료형 뒤에 *를 붙이고 해당 변수에 다른 변수의 메모리 주소 값을 할당해주면 해당 변수는 메모리 주소를 저장하는 포인터가 된다. 포인터 변수는 포인터 변수에 넣을 메모리 주소의 변수가 int형이라면 int *형으로, char형이면 char *로 선언하는 등 담을 메모리 주소의 자료형과 같게 선언해야한다. 왜냐하면 자료형의 크기가 다 다르기 때문에 뒤에 정리한 역참조 연산자로 값을 가져올 때 해당 크기 만큼만 주소를 참조해서 값을 가져와야하기 때문이다.
#include <stdio.h>
int main()
{
int a = 1;
int *p = &a; // a의 주소 값을 포인터 변수 p의 초기 값으로..
printf("%p\n", p); // p 출력
printf("%p", &a); // a의 메모리 주소 출력
}
위는 int형 변수 a를 선언하고 초기 값으로 1을 준 후, 포인터 변수 p를 선언해서 a의 메모리 주소 값을 넣어준 코드다.
%p 서식 지정자로 p와 &a를 각각 출력해보면 동일한 값이 나오는 것을 볼 수 있다. 왜냐하면 p는 a의 메모리 주소 값을 넣어줬기에 p를 출력한다면 당연히 a의 메모리 주소가 출력되고, &a를 출력하면 &는 변수의 메모리 주소를 구해주므로 a의 메모리 주소가 출력되기 때문에 p, &a의 값은 같은 것이다.
int *a = 0x123
참고로 위처럼 포인터 변수에 값을 직접 저장하면 안된다. 포인터는 메모리 주소를 저장하는데, 0x123은 실제 존재하는 메모리 주소가 아니라 오류가 뜨기 때문이다.
단 존재하는 메모리 주소면 포인터에 직접 저장할 수 있다.
#include <stdio.h>
int main()
{
int *p = NULL; // null 포인터 생성
printf("%p", p); // p에 저장된 주소 출력
return 0;
}
위 코드처럼 null 포인터도 생성할 수 있다. null 포인터는 아무것도 가리키지 않는 상태를 뜻한다. 아무것도 가리키지 않기 때문에 아래에 정리한 역참조 또한 할 수 있다.
이 null 포인터는
if (p == NULL) // p가 널 포인터라면
{
p = malloc(sizeof(int)); // 4바이트 메모리 할당
}
위 코드처럼 실무에서 포인터가 NULL이면 아래에 정리한 malloc으로 메모리 할당을 해주는데 쓰인다.
-역참조 연산자 *
앞서 포인터 변수에는 메모리 주소가 저장되어있다 했는데, 역참조 연산자 *를 사용하면 해당 메모리 주소의 공간에 저장된 값에 접근할 수 있다.
참고로 선언할 때도 *를 붙여줘서 햇갈릴 수 있지만 선언할 때의 *는 이 변수가 포인터다라고 알려주는 역할이고, *를 포인터에 사용할 때는 포인터의 메모리 주소를 역참조하겠다는 뜻이다.
int a = 1; // int형 변수 a 선언
int *p; // int형 포인터 변수 p 선ㅇ너
p = &a; // a의 메모리 주소 값을 p에 저장
printf("%d", *p); // p를 역참조해 해당 주소의 값 출력
위처럼 포인터 p에 a의 메모리 주소가 저장되어 있을 때, p 앞에 *를 붙이면 a의 메모리 주소에 저장된 값 1에 접근할 수 있는 것이다.
#include <stdio.h>
int main()
{
int a = 1;
int *p = &a; // a의 주소 값을 포인터 변수 p의 초기 값으로
printf("%d\n", *p); // p에 저장된 주소에 접근하여 값을 출력
*p = 2; // p에 저장된 주소에 접근하여 2를 넣어줌
printf("%d\n", a); // a의 값 출력
}
위는 a의 주소를 포인터 변수 p에 넣어주고 역참조 연산자를 사용하여 p가 가리키는 주소의 값을 출력하고 해당 주소의 값을 변경해보는 코드다.
첫 줄은 예상했듯이 해당 메모리 주소의 값인 a에 저장된 1이 출력되고, 둘째 줄은 a의 값을 출력하게 했지만 신기하게도 1이 아닌 2가 출력된다.
그 이유는 *p = 2;로 a의 메모리 주소에 접근해 해당 공간에 2를 넣어줬기 때문에, a를 출력했을 때 1이 아닌 a의 메모리 주소에 저장돼 있는 2를 출력하게 된 것이다.
위는 코딩도장에 있는 포인터, 역참조에 대한 사진이다.
int num1 = 10;
int *numPtr = &num1;
위 코드처럼 num1의 메모리 주소를 가진 포인터 변수 numPtr이 있을 때, 그냥 numPtr은 num1의 주소만 가리키고 *numPtr은 num1의 주소에 저장된 값 10을 가리킨다는 내용의 사진이다.
#include <stdio.h>
int main()
{
long long int a = 1; // long long int형 a 선언
long long int *b; // long long int형 포인터 b 선언
char c = 2; // char형 c 선언
char *d; // char형 포인터 d 선언
b = &a; // a의 주소를 b에 저장
d = &c; // c의 주소를 d에 저장
printf("%lld %d", *b, *d); // b, d의 주소를 역참조해 값 출력
}
위처럼 여러 자료형에서도 포인터를 사용하고 역참조 연산자를 사용할 수 있다. 방법은 int형과 같다.
-상수와 포인터
상수 변수의 주소를 포인터에 담을 수도 상수형 포인터를 생성할 수도 있다. 상수 변수의 주소를 포인터에 담은 경우 역참조를 해 값을 변경하려 하면 오류가 뜨게 되고, 상수형 포인터를 생성한 경우 선언할 때 담은 메모리 주소를 변경할 수 없다.
-void 포인터
void는 자료형이 정해지지 않은 포인터를 만들 때 사용한다. 범용적으로 쓸 수 있기 때문에 void를 범용 포인터라고도 한다. 선언은 다른 자료형들과 똑같이 void *<포인터 명>으로 하면 된다. 자료형이 다른 포인터끼리 메모리를 저장하면 컴파일 경고가 뜨는 것을 볼 수 있는데 void 포인터는 자료형이 정해지지 않았기 때문에 어떤 자료형의 포인터든 모두 저장할 수 있다. 또한 자료형으로 된 포인터에도 void 포인터를 저장할 수 있다.
단 void 포인터는 자료형이 정해지지 않았기에 역참조로 값을 가져올 수 없다. 왜냐하면 저장할 크기, 가져올 크기를 모르기 때문이다.
#include <stdio.h>
int main()
{
long long int a = 1;
long long int *b;
// long long int형 변수 및 포인터 선언
float c = 1.5f;
float *d = &c;
// float형 변수 및 포인터 선언
void *pointer; // void형 포인터 선언
pointer = &a; // long long int형 변수 a의 주소를 pointer에 저장
b = pointer; // pointer의 주소를 b에 저장
pointer = d; // float형 변수 c의 주소가 담긴 d의 값을 pointer 변수에 저장
printf("%lld %f", *b, *d); // b, d를 역참조해 값 출력
}
위 코드를 실행해보면 어떤 형이든 void 포인터로 경고 없이 메모리 주소를 저장할 수 있는 것을 볼 수 있다.
솔직하게 void 없이 써도 아무 문제 없고, 역참조도 불가능한 void를 어따쓰지 라는 생각이 들었는데, void는 함수에서 매개변수로 다양한 자료형을 받아들이거나 함수의 반환 포인터를 다양한 자료형으로 저장할 때, 자료형을 숨기고 싶을 때 사용한다고 한다.
-디버거에서 포인터 확인
디버거를 사용하면 메모리에서의 변수의 위치, 역참조 등을 쉽게 확인할 수 있다. 디버거에서 메모리를 확인하려면 visual studio 2017 기준으로 디버깅 상태에서 디버그 -> 창 -> 메모리 -> 메모리 1,2,3 중 하나를 선택하면 메모리 맵이 열린다.
#include <stdio.h>
int main()
{
int a = 1;
int *b;
b = &a; // a의 주소를 포인터 b에 저장
*b = 2; // 포인터 b를 역참조해 해당 주소의 값을 2로 변경
printf("%d", a); // a의 값 출력
}
위는 1이 들어간 a의 주소를 포인터 b에 넣고 역참조 연산자를 사용해 *b를 2로 바꿔주는 기능을하는 코드다. 위 코드를 디버깅 할 것이다.(앞에서 디버깅 방법은 다뤘으므로 디버깅 방법은 생략하겠다.)
디버깅 모드에서 7행까지 실행해본 모습이다. 보면 b = &a 코드로 a의 메모리 주소를 포인터 변수 b에 넣어줬고 역참조인 *b는 a의 값인 1을 가리키는 것을 볼 수 있다. 포인터 변수 b가 a의 주소를 갖는 것을 확인했고, 역참조를 사용했을 때 a 주소의 값인 1을 갖는 것을 봤으니 이제 메모리 뷰에서 a의 위치를 확인해볼 것이다.
메모리 뷰에서 a의 메모리 주소로 가보니 a의 값인 1이 들어가 있는 것을 볼 수 있다. 위의 표시된 영역처럼 변수 a는 int형이기에 4바이트 만큼의 크기를 갖는다. 이상하게 00 00 00 01이 아닌 01 00 00 00로 저장되어 있는데, 이는 x86 계열 CPU는 리틀 엔디언 방식이라 값이 거꾸로 저장되는 것이다. 리버싱을 공부해봤다면 알 수 있는 사실이다.
리틀 엔디언 : 값이 거꾸로 저장되는 것(int형 1 = 01 00 00 00)
빅 엔디언 : 값이 순서대로 저장되는 것(int형 1 = 00 00 00 01)
위 상태에서 *b = 2 코드까지 실행하니 01 00 00 00에서 02 00 00 00으로 바뀌는 것을 볼 수 있다. *b = 2 코드는 b가 담고 있는 메모리 주소(a)의 값을 2로 바꿔주는 것이기 때문에 a의 주소의 값이 02 00 00 00으로 바뀌는 것이다.
참고로 하단의 메뉴에서 조사식 1으로 가면 위처럼 알고 싶은 변수의 값을 찾을 수 있다. &a를 입력하니 a의 메모리 주소가 나왔고, b를 입력하니 현재 시점에서 b에 담겨 있는 a의 메모리 주소가 값으로 나왔다.
-이중 포인터
포인터의 포인터, 즉 이중 포인터도 가능하다. **<이중 포인터 명> 이렇게 *를 두 개 붙여서 이중 포인터를 선언할 수 있다.
#include <stdio.h>
int main()
{
int a = 1; // int형 변수
int *b = &a; // int형 포인터 선언 후 a의 메모리 주소 값을 저장
int **c = &b; // int형 이중 포인터 선언 후 포인터 b의 메모리 주소 값을 저장
printf("%p %p %p\n", &a, b, c); // a, b, c의 메모리 주소 출력
**c = 2; // c를 역참조해 해당 주소의 값을 2로 변경
printf("%d", a); // a의 값 출력
}
위는 이중 포인터를 사용해 int형 변수 a의 메모리 주소를 가진 포인터 b의 메모리 주소를 c에 저장한 코드다.
실행 결과를 보면 &a, b는 같지만 c는 다른 것을 볼 수 있다. 왜냐하면 b 역시 포인터 변수이므로 b가 가진 값이 저장되는 메모리 공간이 존재한다. 이중 포인터 c는 이 공간의 주소를 저장하기 때문에 &a, &b와는 다르게 나온 것이다.
또한 **c = 2를 하면 a의 값이 2로 변경되는 것을 볼 수 있는데, 그 이유는 보통 역참조는 *<포인터>로 하지만 c는 이중 포인터이므로 *를 두 번써 역참조의 역참조를 해 결국 a의 값을 가리키게 되는 것이다.
이런식으로 이중, 삼중, 사중 등 그 이상으로 포인터를 사용할 수 있다.
Unit 35. 메모리 사용하기
-메모리 사용-
위에서 포인터에 메모리 주소를 저장하는 방식으로 포인터를 사용했는데, 이번에는 포인터에 원하는 만큼의 메모리를 할당 받아본다.
메모리는 malloc -> 메모리 사용 -> free 패턴으로 사용한다.
-malloc, free
malloc는 힙의 메모리 공간을 확보해주는 함수다. 사용은 <포인터> = malloc(<확보할 크기>)로 한다.
free는 malloc 함수로 힙에 할당한 메모리를 해제해준다.
이 두 함수는 stdlib.h를 include해야지만 사용이 가능하다.
스택은 리버싱을 공부해보며 배웠지만 힙의 개념은 생소해서 따로 찾아봤다.
스택은 각 기능(변수 선언 같은)에 의해 생성 된 임시 변수를 저장하는 공간이고 자동으로 CPU에 의해 관리된다. 반면에 힙은 자동으로 관리되지 않는 컴퓨터 메모리 영역이다. 또한 한정된 영역에서 값에 접근할 수 있는 스택과 달리 힙은 전역적으로 접근이 가능하다.
스택은 자동으로 관리되기 때문에 변수를 선언 후 스택 프레임이 끝날 때 자동으로 할당하고 지워주지만(cdcel 같은) 힙은 관리되지 않기 때문에 반드시!! free 함수 등으로 할당을 해제해 줘야한다. 만약 해제해주지 않는다면 할당한 메모리들이 쌓이고 쌓여 결국 시스템의 메모리가 부족해지므로 강제로 종료되거나 메모리 할당에 실패하게 된다. 또한 메모리 사용량이 계속 증가하는 메모리 누수가 발생할 수 있다.
결론적으로 일반 변수들을 선언하면 스택에 생성되지만 malloc 함수로 메모리를 할당하면 스택이 아닌 힙 부분의 메모리를 사용한다. (참고 : 스택, 힙의 이해)
위 사진처럼 스택은 LIFO고 큰 주소에서 작은 주소로 자라지만 힙은 이 반대 방향으로 자란다고 한다.(운영체제 및 플렛폼에 따라 달라질 수 있다.)
#include <stdio.h>
#include <stdlib.h> // malloc, free 함수를 사용하기 위해 헤더 include
int main()
{
int *p; // int형 포인터 변수 선언
p = malloc(sizeof(int)); // malloc 함수로 4바이트 크기(int)의 메모리 공간 확보
printf("%p", p); // p의 메모리 주소 출력
free(p); // 메모리 해제
}
위는 malloc 함수로 4바이트(int 자료형의 크기)만큼 힙에 메모리 공간을 확보한 후 free 함수로 해제한 코드다.
malloc 함수로 성공적으로 메모리 공간을 확보했기에 포인터 p에는 힙에 확보한 메모리 주소 값이 저장돼 있다.
이렇게 malloc 함수로 메모리 공간을 확보하면 원하는 시점에 원하는 만큼의 메모리를 할당할 수 있기에 동적 메모리 할당이라 부른다.
#include <stdio.h>
#include <stdlib.h>
int main()
{
int *p; // int형 포인터 변수 p 선언
p = malloc(sizeof(int)); // malloc 함수로 4바이트 크기(int)의 메모리 공간 확보
*p = 10; // p를 역참조해 해당 주소에 10을 저장
printf("%d", *p); // p를 역참조해 해당 주소에 저장된 값을 출력
free(p); // 메모리 해제
}
위는 malloc으로 힙에 메모리 공간을 할당하고 그 위치를 담고 있는 포인터 변수 p를 역참조해 해당 위치에 10을 넣은 코드다.
p를 역참조 해 해당 위치에 10을 넣어줬기에 p가 가리키는 메모리 주소에 담긴 값인 10이 출력된다.
-memset
memset을 사용하면 메모리의 내용을 원하는 크기만큼 특정값으로 설정할 수 있다. 이때 설정하는 크기는 바이트 단위다.
사용은 memset(<포인터>, <값>, <크기>);로 하면 된다.
#include <stdio.h>
#include <stdlib.h>
#include <string.h> // memset을 사용하기 위해 include
int main()
{
int *p; // int형 포인터 변수 p 선언
p = malloc(sizeof(int)); // malloc 함수로 4바이트 크기(int)의 메모리 공간 확보
memset(p, 0x11, 4); // p가 가리키는 주소에 4바이트 만큼 0x11를 할당
printf("%x %d", *p, *p); // p를 역참조해 해당 주소에 저장된 값을 출력
free(p); // 메모리 해제
}
위는 malloc 함수로 힙에 4바이트 크기의 메모리 공간을 확보하고 그 주소를 p에 저장한 후 memset 함수로 p가 가리키는 주소에 4바이트 만큼 0x11로 설정한 코드다.
힙에 확보된 4바이트 크기의 메모리에 memset 함수로 4바이트 만큼 0x11로 값을 넣어줬으니 해당 메모리는 11 11 11 11이 된다. 따라서 16진수 11111111과 10진수 286331153이 출력된다.
bp를 걸고 디버깅해보면 쓰레기 값이 들어가 있던 p의 주소가
memset 함수를 사용하니 11 11 11 11로 바뀌는 것을 알 수 있다.
Unit 34 ~ Unit 35 문제
포인터는 자료형과 변수명 사이에 *를 붙여서 선언하면 된다.
답 : c
메모리 주소는 변수 앞에 &를 붙이면 구할 수 있다.
답 : d
포인터 변수의 역참조는 변수명 앞에 *를 붙여주면 된다.(선언 제외)
답 : a
부호 없는 : unsigned
long 포인터 : long *<포인터 명>
따라서 unsigned long *num1;로 선언해주면 된다.
답 : e
다중 포인터는 중첩의 개수만큼 *를 붙여주면 된다.
답 : d
빈 칸을 채워서 10, 20이 각 줄에 출력되게하는 것이 목적이다. num1, num2에 각각 10, 20이 저장돼 있고 출력문은 포인터 변수 numPtr의 역참조 값을 출력하므로 numPtr에 num1, num2의 메모리 주소를 각각 받으면 될 것 같다.
#include <stdio.h>
int main()
{
int *numPtr;
int num1 = 10;
int num2 = 20;
numPtr = &num1; // num1의 메모리 주소 저장
printf("%d\n", *numPtr);
numPtr = &num2; // num2의 메모리 주소 저장
printf("%d\n", *numPtr);
return 0;
}
첫 번째 출력문 위에는 numPtr = &num1;으로 num1의 메모리 주소를.. 두 번째 출력문 위에는 numPtr = &num2;로 num2의 메모리 주소를 저장했다.
입력 된 정수를 2중 포인터로 출력하는 것이 목적이다. 입력한 정수는 num1에 저장되니 num1의 메모리 주소를 포인터 numPtr1에 저장하고 numPtr1의 메모리 주소를 numPtr2에 저장하면 될 것 같다.
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int *numPtr1;
int **numPtr2;
int num1;
scanf("%d", &num1);
numPtr1 = &num1; // num1의 메모리 주소 numPtr1에 저장
numPtr2 = &numPtr1; // numPtr1의 메모리 주소 numPtr2에 저장
printf("%d\n", **numPtr2);
return 0;
}
출력 전에 numPtr1에 num1의 메모리 주소를 저장하고, 이 numPtr1의 메모리 주소를 numPtr2에 저장했다. 이러면 출력문에서 numPtr2를 두 번 역참조해 num1의 값이 출력되게 된다.
메모리 할당은 malloc 함수로 해줄 수 있다. 포인터를 선언 후 <포인터> = malloc(<원하는 크기>)
답 : d
c언어에서 메모리를 사용하는 패턴은 malloc -> 사용 -> free다.
답 : c
포인터에 malloc으로 메모리를 할당해줬을 경우 *<포인터> = <값>을 작성하면 할당한 메모리에 원하는 값을 저장할 수 있다.
답 : b
memset 함수는 1바이트 단위로 메모리를 초기화한다.
답 : a
memset 함수가 선언된 헤더파일은 string.h다.
답 : d
아무것도 가리키지 않는 포인터는 null pointer(널 포인터)다.
malloc 함수로 numPtr1, numPtr2에 각각 4바이트, 8바이트 만큼 메모리를 할당하는 것이 목적이다.
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
int main()
{
int *numPtr1 = malloc(sizeof(int));
long long *numPtr2 = malloc(sizeof(long long));
// 4, 8바이트 크기의 포인터 선언 후 4, 8바이트 크기의 메모리 할당
*numPtr1 = INT_MAX;
*numPtr2 = LLONG_MAX;
printf("%d %lld\n", *numPtr1, *numPtr2);
free(numPtr1);
free(numPtr2);
return 0;
}
sizeof로 각 자료형의 크기를 구해 그만큼의 메모리를 할당해줬다.
두 정수가 int형 변수 num1, num2에 각각 입력되는데, 포인터를 선언하고 메모리를 할당해 각각에 담긴 값들을 numPtr1, numPtr2에 할당된 메모리에 저장하는 것이 목적이다.
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
int main()
{
int num1;
int num2;
int *numPtr1 = malloc(sizeof(int));
int *numPtr2 = malloc(sizeof(int));
//4바이트 크기의 포인터 변수 선언 후 4바이트 크기의 메모리 할당
scanf("%d %d", &num1, &num2);
*numPtr1 = num1;
*numPtr2 = num2;
printf("%d\n", *numPtr1 + *numPtr2);
free(numPtr1);
free(numPtr2);
return 0;
}
int형 변수를 2개 선언하고 malloc 함수로 4바이트 크기의 메모리를 할당해줬다.
'Old (2021.01 ~ 2021.12) > Programming' 카테고리의 다른 글
c언어 코딩도장 Unit 39 ~ Unit 41 (0) | 2021.03.13 |
---|---|
c언어 코딩도장 Unit 36 ~ Unit 38 (0) | 2021.03.06 |
c언어 코딩도장 Unit 24 ~ 33 (0) | 2021.02.21 |
c언어 코딩도장 Unit 12 ~ 23 (4) | 2021.02.17 |
c언어 코딩도장 Unit 1 ~ 11 (0) | 2021.02.16 |