독하게 시작하는 C 프로그래밍 2 – 널널한 개발자 TV
https://www.youtube.com/watch?v=t84iSMKJGiU&list=PLXvgR_grOs1AQuQ-5mWbx0zdG0betdeoL&index=17
- 독하게 시작하는 C 제11장 메모리와 포인터 – 첫 번째
- 독하게 시작하는 C 제11장 – 두 번째
- 독하게 시작하는 C 제11장 – 세 번째
- 독하게 시작하는 C 제11장 – 네 번째
- 독하게 시작하는 C 제11장 – 다섯 번째
- 독하게 시작하는 C 제11장 – 여섯 번째
- 독하게 시작하는 C 제12장 함수 응용 – 첫 번째
- 독하게 시작하는 C 제12장 – 두 번째
독하게 시작하는 C 제11장 메모리와 포인터 – 첫 번째
# 링커 – 고급 임의 기준 주소 – 아니오
# 실행마다 매번 주소 영역 변경 ASLR
Address Space Layout Randomization.
메모리 분류와 특징
스택 : 자동변수이고 지역변수인 변수가 사용하는 메모리 영역.
힙 : 동적 할당할 수 있는 자유 메모리 영역
PE 파일(실행파일) :
텍스트 섹션 : C언어 소스코드가 변역된 기계어가 저장된 메모리 영역
데이터 섹션 :
읽기 전용 : 상수 형태의 문자열이 저장된 메모리 영역
읽기/쓰기 : 정적변수나 전역변수가 사용하는 메모리 영역.
# 포인터 변수 : 메모리의 주소를 저장하기 위한 전용 변수
# 함수 이름은 주소이다.
# 함수 내부에서는 포인터를 쓰는 일이 별로 없다.
함수가 여러개 일 때 포인터가 빛을 발한다.
#include <stdio.h>
int main(void)
{
//int nData = 300; // 직접 지정
//int* pnData = &nData; // 간접 지정
//pnData += 2; // int 만큼 이동
//*pnData = 300;
int nData = 300;
int* pnData = &nData;
*((int*)0x0018FF28) = 600; // 직접 지정
*pnData = 600; // 간접 지정
return 0;
}
// 함수 포인터
#include <stdio.h>
// void (*)(int)
void TestFunc(int nParam)
{
printf("TestFunc() : %d\n", nParam);
}
int main(void)
{
TestFunc(10); // High level
((void (*)(int))0x004113C)(10);
((void (*)(int))4264896)(10); // Low level
return 0;
}
// 포인터를 사용하는 주된 예
#include <stdio.h>
void TestFunc(int *paList)
{
for (int i = 0; i < 5; ++i)
printf("%d\t", paList[i]);
putchar('\n');
}
int main(void)
{
int aList[5] = { 40, 20, 50, 30, 10 };
TestFunc(aList);
return 0;
}
독하게 시작하는 C 제11장 – 두 번째
#include <stdio.h>
int main(void)
{
int aList[5] = { 40, 20, 50, 30, 10 };
int* paList = aList;
paList + 1; // 기준 주소 + 정수(옵셋) -> 상대 주소
*(paList + 1); // int형 변수로 지정된다.
*(paList + 1) = 5; // 같다.
paList[1] = 5; // 같다.
TestFunc(aList);
return 0;
}
#include <stdio.h>
int main(void)
{
int aList[5] = { 40, 20, 50, 30, 10 };
int* paList = aList;
printf("%p\n", aList); // 같다.
printf("%p\n", aList + 1); // 같다.
printf("%p\n", &aList[1]); // 같다.
return 0;
}
#include <stdio.h>
int main(void)
{
int aList[5] = { 40, 20, 50, 30, 10 };
int nTotal = 0;
for (int i = 0; i < 5; ++i)
nTotal += aList[i];
printf("%d\n", nTotal);
// 비추천. 직관적이지 않다.
nTotal = 0;
int* pnData = aList;
while (pnData < aList + 5)
{
nTotal += *pnData;
pnData++;
}
printf("%d\n", nTotal);
return 0;
}
배열의 이름은 0번 요소의 주소이며, 전체 배열을 대표하는 식별자입니다.
포인터 변수는 주소를 저장하기 위한 변수이다.
배열의 이름이 주소이므로 포인터 변수에 저장할 수 있다.
int aList[3]; //
aList : 주소상수
int : 요소의 형식
int* 이름 = aList;
배열은 주소로 식별 : 주소에 이름을 붙인다. : 배열의 이름
배열의 이름은 주소이다.
int aList[3] = {1,2,3};
&aList[0] // 첫번째 요소 주소
&aList[1]
&aList[2]
aList + 1 // 같다.
&*(aList +1) // 같다.
&(주소)*(변수)(상대주소)
독하게 시작하는 C 제11장 – 세 번째
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int* pList = NULL, i = 0;
char* pChar = NULL;
// 12바이트 메모리를 동적 할당하고 시작 주소를 pList 포인터 변수에 저장
pList = (int*)malloc(sizeof(int) * 3);
// 동적 할당한 대상 메모리를 배열 연산자로 접근한다.
pList[0] = 10;
pList[1] = 20;
pList[2] = 30;
for (i = 0; i < 3; i++)
{
printf("%d\n", pList[i]);
}
free(pList);
return 0;
}
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int* pList = NULL;
pList = malloc(sizeof(int) * 3); // 12 // fd fd fd fd 영역으로 둘러 싸임
pList[0] = 10;
pList[1] = 20;
pList[2] = 30;
*(((char*)pList) + 12) = 'A'; // 뒷부분 fd fd fd fd 영역을 수정해본다.
free(pList); // 해제할 때 Heap 손상 에러 뜸. (fd fd fd fd 는 비주얼 스튜디오 개발자가 만든 것일 것이다.)
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
int* pList = NULL;
int* pList2 = NULL;
pList = (int*)malloc(sizeof(int) * 3); // 12
memset(pList, 0, sizeof(int) * 3); // 메모리의 값을 0으로 초기화, string.h 인클루드
pList2 = (int*)calloc(5, sizeof(int)); // 개수와 크기 지정, 0으로 초기화 해 줌
pList[0] = 10; // 배열처럼 사용한다.
pList[1] = 20;
pList[2] = 30;
free(pList);
free(pList2);
return 0;
}
// _msize() 함수 (malloc.h)
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
int main(void)
{
char* pszData = NULL;
pszData = (char*)malloc(sizeof(char) * 6);
printf("%d\n", _msize(pszData));
free(pszData);
return 0;
}
Stack : 1MB, 스코프 영역, 일반적인 변수
Heap : 자유 영역 (32비트 일 때 1.7GB), 동적 할당
# PE image
text (code) : 기계어
data : RW 전역변수(정적변수), Read only : 문자열 상수
# 동적 할당 : Heap 영역 // 할당 받는다. 반환 한다.
# void *malloc(size_t size)
인자 : 할당받을 메모리의 바이트 단위 크기
반환값 : 힙 영역에 할당된 메모리 덩어리 중 첫 번째 바이트 메모리의 주소
설명 : 할당받은 메모리는 반드시 free() 함수로 반환해야 하며, 메모리를 초기화하려면 memset() 함수를 이용해야한다.
기본적으로 쓰레기 값이 들어있다.
// malloc 으로 메모리를 할당하면 void * 이다. 이것을 int * 등으로 강제형변환해서 사용한다.
// void * 는 포인터인 것은 맞지만 이 주소가 가리키는 대상 메모리를 어떤 형식으로 해석할 지는 아직 결정되지 않았음.
// void 는 길이와 해석방법이 없음을 의미한다.
// malloc() 은 속도가 느리다. 계속 조금씩 할당받는 것보다 500MB 바이트 정도 크게 할당받아서 예약 걸어놓고 쓰는게 더 낫다. (메모리 풀)
# void free(void *memblock);
인자 : 반환할 메모리의 주소
반환값 : 없음
설명 : 동적으로 할당받은 메모리를 운영체제에 반환하는 함수
// fd fd fd fd 영역으로 둘러싸인 곳을 반환.
// free 할 때는 사이즈를 안줘도 된다. 왜냐하면 내부적으로 알고 있기 때문이다.
# 접근과 사용은 기존의 배열이랑 완전 똑같다.
# memset(pList, 0, sizeof(int) * 3); // string.h 인크루드
# void* calloc(개수, 크기) // 메모리 할당 후 0으로 초기화해줌
(int*)calloc(5, sizeof(int)); // 20
# 동적 할당된 메모리 크기 확인 :
_msize() // 윈도우 (malloc.h 인클루드)
// 유닉스는 malloc_usable_size() 함수
독하게 시작하는 C 제11장 – 네 번째
배열은 상수이다. rvalue 이다.
주소만 복사 vs 내용을 복사
Shallow Copy vs Deep Copy
int nData = 10;
int *pnData = &nData;
int &rData = nData; // 참조형 C++
// 초기화 필수, 포인터가 맞지만 포인터 값을 변경 불가능
// 포인터가 가진 문제 해결 위해서 나옴
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
int main(void)
{
char szSrcBuf[12] = "Hello";
char szDstBuf[12] = { 0 };
// szSrcBuf = szDstBuf; 배열은 상수이므로 rvalue이다. 3 = 4 한 것과 같다. 주소 = 주소 한 것과 같다.
char* pszData = malloc(sizeof(char) * 12);
// Deep Copy -> 내용을 복사
memcpy(szDstBuf, szSrcBuf, sizeof(szDstBuf));
// Shallow Copy -> 주소만 복사
// pszData = szSrcBuf; // 동적 할당 메모리 소실, 스택을 가리키게 됨, free 하면 스택 날라감. szSrcBuf 는 자동으로 관리되는 메모리
// Deep Copy
memcpy(pszData, szSrcBuf, sizeof(char) * 12);
puts(pszData);
free(pszData);
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
int main(void)
{
char szSrcBuf[12] = "Hello";
char szDstBuf[12] = { 0 };
memcpy(szDstBuf, szSrcBuf, sizeof(szDstBuf));
// if (szSrcBuf == szDstBuf) // 주소 비교
if (memcmp(szSrcBuf, szDstBuf, sizeof(szSrcBuf)) == 0) // 내용 비교
puts("Same");
else
puts("Diff");
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
int main(void)
{
char szBuffer[12] = { "I am a boy." };
char* pszFound = strstr(szBuffer, "am");
int nIndex = pszFound - szBuffer; // 검색 문자열의 인덱스 구하기
return 0;
}
realloc() : 이미 할당된 메모리를 재할당하는 함수
void *realloc(void *memblock, size_t size);
인자 : 기존 동적 할당된 메모리 주소, 다시 할당 받을바이트 단위 크기
반환값 : 다시 할당된 메모리 덩어리 중 첫 번째 바이트의 메모리 주소
설명 : 만일 이미 할당된 메모리 영역에서 크기를 조정할 수 있다면, 반환된 주소는 첫번째 인자로 전달된 주소와 같다.
그러나 불가능하다면 기존의 메모리를 해제하고 새로운 영역을 할당한 후 새로 할당된 메모리의 주소를 반환한다.
// 확인 필요
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
int main(void)
{
char* pszBuffer = NULL, * pszNewBuffer = NULL;
pszBuffer = (char*)malloc(12);
sprintf(pszBuffer, "%s", "TestString");
printf("[%p] %d %s\n", pszBuffer, (int)_msize(pszBuffer), pszBuffer);
pszNewBuffer = (char*)realloc(pszBuffer, 32);
if (pszNewBuffer == NULL)
free(pszBuffer);
sprintf(pszNewBuffer, "%s", "TestStringData");
printf("[%p] %d %s\n", pszNewBuffer, (int)_msize(pszNewBuffer), pszNewBuffer);
free(pszNewBuffer);
return 0;
}
// char 형 배열에서 앞부분 4바이트만 int 형으로 변환
#include <stdio.h>
int main(void)
{
char szBuffer[12] = { "I am a boy." };
int nData = *((int*)szBuffer);
printf("%d\n", nData);
return 0;
}
독하게 시작하는 C 제11장 – 다섯 번째
#include <stdio.h>
int main(void)
{
char* astrList[3] = { "Hello", "World", "String" };
printf("%s\n", astrList[0]);
printf("%s\n", astrList[1]);
printf("%s\n", astrList[2]);
printf("%s\n", astrList[0] + 1);
printf("%s\n", astrList[1] + 2);
printf("%s\n", astrList[2] + 3);
printf("%c\n", astrList[0][3]);
printf("%c\n", astrList[1][3]);
printf("%c\n", astrList[2][3]);
return 0;
}
Hello
World
String
ello
rld
ing
l
l
i
int main(void)
{
char ch = 'A';
// char*에는 char형의 주소를 담는다.
char *pData = &ch;
// char**에는 char*형의 주소를 담는다.
char* *ppData = &pData;
// char***에는 char**형의 주소를 담는다.
char** *pppData = &ppData;
printf("%c\n", ch);
printf("%c\n", *pData);
printf("%c\n", **ppData);
printf("%c\n", ***pppData);
return 0;
}
A
A
A
A
독하게 시작하는 C 제11장 – 여섯 번째
int *pnData 가 가리키는 대상은 배열일 수도 있고 한 개 일 수도 있다.
int *pnData;
2번(대상, 형식) 1번(포인터변수)
# 다차원 배열 -> 배열의 배열 (같은 형식의 자료가 여럿)
char aListUser[3][12] = {}
char 형이 12개 있는 배열 3개
char (*pList)[12] = aListUser
요소 (포인터 변수) 12개
char** pList = aListUser // 2중 포인터는 틀림
#include <stdio.h>
int main(void)
{
char astrList[2][12] = { "Hello", "World" };
char (*pstrList)[12] = astrList;
puts(pstrList[0]);
puts(pstrList[1]);
return 0;
}
// 다차원 배열 포인터로 전달
#include <stdio.h>
void PrintUser(char(*pUser)[12])
{
for (int i = 0; i < 3; i++)
puts(pUser[i]);
}
int main(void)
{
char aListUser[3][12] = { "철수", "길동", "영희" };
PrintUser(aListUser);
return 0;
}
# 전역변수, static 변수는 동시성 문제가 생긴다. (멀티쓰레드)
#include <stdio.h>
//int nData = 10; // 전역변수
void TestFunc(void)
{
// 지역변수이지만 전역변수와 같다.
// 접근성은 TestFunc() 내부로 제한된 지역변수이다.
// 기억부류는 스택이 아니라 데이터 영역인 정적변수 선언 및 정의
// 정의는 이 함수가 여려번 호출되더라도 단 한번만 적용된다.
static int nData = 10;
//정적변수이므로 이 변경된 값은 함수가 반환하더라도 유지된다.
printf("%d\n", nData++);
}
int main(void)
{
TestFunc();
TestFunc();
TestFunc();
return 0;
}
# 레지스터 변수 (CPU)
임베디드 운영체제나 H/W를 위한 프로그램을 제작하는 경우가 아니라면 크게 따질 필요는 없습니다.
레지스터 변수는 CPU의 일부이므로 별도의 주소가 없다.
CPU의 일부인 레지스터는 일반 메모리와 달리 주소가 아니라 고유명사로 식별된다.
일반 변수처럼 주소 연산을 수행할 수 없다.
register int x = 5;
&x; // 레지스터 변수는 주소가 없으므로 안됨.
7. 독하게 시작하는 C 제12장 함수 응용 – 첫 번째
# Call by Reference (주소 전달)
– 함수의 매개변수(포인터)로 주소를 받는 방식입니다.
#include <stdio.h>
void TestFunc(int * pnParam)
{
*pnParam = 10;
}
int main(void) {
int nData = 0;
TestFunc(&nData);
printf("%d\n", nData);
return 0;
}
# Swap 함수
#include <stdio.h>
void Swap(int *pLeft, int *pRight)
{
int nTmp = *pLeft;
*pLeft = *pRight;
*pRight = nTmp;
}
int main(void) {
int a = 10, b = 20;
Swap(&a, &b);
printf("%d, %d\n", a, b);
return 0;
}
# 잘못된 주소 전달
– 운영체제에 반환했거나 곧 사라질 메모리에 대한 주소를 반환하는 일은 없어야 합니다.
#include <stdio.h>
int* TestFunc(void)
{
int nData = 10;
return &nData; // 함수가 반환하면 소멸할 자동변수의 주소를 반환한다.
}
int main(void)
{
printf("%d\n", *TestFunc()); // 포인터가 가리키는 대상 메모리는 유효하지 않은 메모리이다.
return 0;
}
# 스택 프레임
자동변수는 스택 영역 메모리는 사용합니다.
자동변수는 기억부류 auto로 선언된 변수를 말하는데 별도로 명시하지 않는 모든 지역변수는 모두 자동변수입니다.
함수의 매개변수 역시 자동변수이고 함수의 지역변수입니다.
따라서 함수 내부에 선언된 변수와 매개변수는 기본적으로 스택을 사용합니다.
스택은 선형 자료구조의 일종으로 정보를 층층히 쌓아 올린 구조입니다.
매개변수는 오른쪽부터 스택에 그리며 새 스코프는 기존 스택 위에 그린다.
# 스택에 쌓이는 순서
변수는 선언한 순서로 쌓인다.
매개변수는 변수보다 먼저 들어간다.
함수가 반환하면 변수와 매개변수는 자동으로 사라진다. (자동변수)
# 스코프가 닫히면 그림에서 지운다.
함수의 몸체는 스코프로 만들어집니다.
그리고 스코프 내부에 제어문 등에 의해 또 다른 스코프가 중첩될 수도 있습니다.
그리고 스코프 내부에서 변수를 선언할 수 있는데 이때 변수의 이름은 중복될 수도 있습니다.
이와 같이 이름이 중복될 경우 식별자를 검색하는 순서는 가장 최근에 형성된 스코프가 우선한다.
#include <stdio.h>
int main(void)
{
int nInput = 0;
scanf("%d", &nInput);
if (nInput > 10)
{
int nInput = 20;
printf("%d\n", nInput);
if (nInput < 20)
{
int nInput = 30;
printf("%d\n", nInput);
}
}
printf("%d\n", nInput);
return 0;
}
독하게 시작하는 C 제12장 – 두 번째
스택은 아래에서 위로 진행
콜 스택
# 재귀호출의 문제
스택은 기본적으로 1MB 인데 무한 루프시 스택오버플로우 발생
#include <stdio.h>
int GetFactorial(int nParam)
{
int nResult = 0;
if (nParam == 1) return 1;
nResult = nParam * GetFactorial(nParam - 1);
return nResult;
}
int main(void)
{
printf("5! = %d\n", GetFactorial(5));
return 0;
}