패스트터틀

[basic] c 배열 포인터 메모리 본문

Development language/c

[basic] c 배열 포인터 메모리

SudekY 2019. 8. 14. 11:26

본 포스팅은 목차순서에 연결성이 없고 단지 궁금증을 풀고 순서없이 적은 목차입니다.

목차-----------------------------------------------

-포인터가 필요한이유?

-포인터에서 *int 와 *char의 차이점

-배열과 포인터 메모리

-배열과 메모리 헷갈리니까 메모리구조에서 확인하기

-메모리상에서 숫자 읽기

-메모리 숫자를 거꾸로 읽는이유

-scanf 말고 scanf_s 사용이유

-형변환은 왜필요한가?

-포인터 형변환은 왜 필요한가?

-포인터* = 포인터*는 무슨의미인가, 또한 선언과 동시에 int 포인터* = 무엇인가?

-포인터 형변환

-동적할당

-sizeof(int),sizeof(int*)

-1차원 동적할당 2차원 동적할당 3차원 동적할당

-NULL 포인터 역참조는 왜 발생하는가

-MMU란?

-segmentation 그리고 paging은 무엇인가?

-내부단편화,외부단편화란 무엇인가?

-segmentation fault는 왜 발생하는가?

-*,**,***,****의 차이

-컴파일(compile)과 런타임(runtime)차이점

-malloc()

-qsort()와 cmpfunc()

-memset()

-----------------------------------------------------

-기타 잡다한 정보

-----------------------------------------------------

 

 

포인터가 필요한이유?

 

https://hjnote.tistory.com/entry/Pointer-Pointer%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0

 

[Pointer] Pointer를 사용하는 이유

[Pointer] Pointer를 사용하는 이유 이번 게시글 부터는 C/C++에서 사용되는 포인터에 대해서 알아 볼 것이다. 프로그래밍을 처음 접하는 프로그래머들이 많이 헤매는 부분이기 때문에 최대한 자세히 설명하려고..

hjnote.tistory.com

1. 지역성극복

2. 연속된 메모리의 참조

3. 힙영역참조

4. 재귀적함수

 

 

포인터에서 *int 와 *char의 차이점

 

 

d2(16) = 210(10) 인데 210을 2의보수을 포함하는 형태로 바꿀경우에는 -46으로 표시됨 이것을 우리는 오버플로우(overflow) 됬다고함

 

unsigned *char말고 unsigned *short(2byte) 로 할때는

 

d2 02 96 49 에서

 

d2 02 만을 읽어오면 읽어오는순서는 02 d2순서대로 읽어온다.(리틀 엔디안방식)

 

*int *char *short 등등 차이는 불러오는 주소값을 1바이트냐 2바이트냐 4바이트냐를 정하는거이고

 

이와더불어 불러온값을 int,char,short로 한정하여 담는것이다.

 

예를들어서 char* 일경우 1바이트를 불러오고 그 값을 범위또한 char 로 한정하여 담음

( 왜 이런식으로 하는지는 모르겠음ㅇㅇ)

 

그리고 char의 범위는 -128~127 이기 때문에 d2를 불러올때 d2는 char형태로 변환되어서 불러옴

 

결론적으로 불러오는 주소의 양과 불러오는 데이터의 범위가 차이가 있는것

 

그러하여 포인터 형변환이 필요하다.

 

 

 

 

배열과 포인터 메모리

 

 

배열 ch[20]에서 ch는 주소값을 나타낸다.

 

1. t를 출력하면 변수가 아닌 주소가 출력이된다.

2. 포인터랑 똑같이 변수를 출력할경우 주소값이 나온다.

 ---   아니다. 틀리다.(아래사진)

 

 

포인터와 배열은 다르다.

3. 스택영역에서 어떻게 찾는지는 잘모르지만 스텍을 쌓는방식으로 찾는것이 아닐까 예상함

 

배열과 메모리 헷갈리니까 메모리구조에서 확인하기

#include 

void main(int argc, char* argv[]) { 

char t[20] = "asdasd"; 

int* a = 2; 
int b = 4; 
a = &b; 

printf("%p\n", a); 
printf("%p\n", &a); 
printf("%d\n", *a); 

}

 

에서 포인터 a의 주소는 

 

0x0025FC68에있고 그 안에 54 fc 25 00 (역순으로) 되어있음

그리고 0x0025FC54에는 변수값이 들어감

 

&n1을 명시안해줘도 찾아보면은 포인터자체의 메모리주소가 있다는것을확인할수있음(0x0029f860)

 

메모리상에서 숫자 읽기

 

123            = 7b 00 00 00   >>> 7b

1234          = d2 04 00 00   >>> 04 d2

123456       = 40 e2 01 00   >>> 01 e2 40 

1234567890 = d2 02 96 49   >>> 49 96 02 d2 

 

4294967295
4294967296

메모리 숫자를 거꾸로 읽는이유

 

이유 : 

https://namu.wiki/w/%EC%BB%B4%ED%93%A8%ED%84%B0%EC%97%90%EC%84%9C%EC%9D%98%20%EC%88%98%20%ED%91%9C%ED%98%84#s-2.1.1

 

컴퓨터에서의 수 표현 - 나무위키

일반적으로 컴퓨터에서 사용되는 정수형의 종류는 다음과 같다. 형크기char8비트short16비트int32비트long32비트 각 정수는 음수를 표현할 수 없고 양수 크기가 두 배로 지원되는 unsigned 형을 가진다. 위 표의 크기는 32비트 윈도우를 기준으로 한 것이다. 운영 체제, CPU 아키텍처, 프로그래밍 언어에 따라 크기나 형의 이름이 다를 수 있다. 예를 들면 같은 윈도 시스템에서도 .NET Framework의 long형은 64비트이다. 정수형

namu.wiki

char t[20] = "asdasd"

 

 

비정상적 배열 선언

scanf 말고 scanf_s 사용이유

scanf_s는 입력시 메모리크기를 설정해주는것이 scanf와 차이가있는데 이렇게 메모리크기를 설정해주는것은

버퍼 오버플로우를 막아주기때문이다.

보안공부를 하면서 버퍼 오버플로우가 얼마나 위험한것임을 알고있기때문에 위험성은 인지하고있었다.

근데 사실 이것에 대해서는 전혀 몰랐는데 community를 설치하고 c를 컴파일할때 scanf를 사용하자마자

scanf_s를 사용하라는 경고로 실행을 할수없어서 왜 visual studio에서 이것을 권고하는지 찾아보다가 알게되었다. 권고사항이라고 하니 그냥 사용해야겠다. 

 

형변환은 왜필요한가?

 

원래 컴파일러가 자동으로 형변환을 알아서 해준다.

num1 = num2일경우에는 num2가 num1의 변수에 맞춰서 자동으로 해줌

(연산자를 기준으로 오른쪽에서 왼쪽으로 형변환)

그렇다면 자동으로 형변환을 알아서 해주는데 왜필요한가?

http://blog.naver.com/PostView.nhn?blogId=ljc1928&logNo=220606601156

 

11. 자료형 변환

다양한 자료형이 있는 C 언어에서 복합적인 연산 수식이 요구될 때는 가급적 같은 자료형 간에 연산이 이...

blog.naver.com

이곳에서 나와있듯이 형변환을 하지않아도 자동으로 되긴 되지만 하지않았을경우

값의 손실을 가져올수있기때문이다.

 

 

포인터 형변환은 왜 필요한가?

 

우선 기존에 형변환이 필요한 이유는 자동으로 변환되지만 프로그래머가 의도한 값을 메모리에 넣고 싶거나 출력하기 위해서였다.

 

그렇다면 포인터 형변환은 왜 필요한것일까?

 

결론은 찾아보니 형변환을 하지 않아도 된다고 한다.

왜냐하면 컴파일러가 자동으로 바꿔주기 때문이다.

 

하지만 하라고 권장한다.

 

https://untitle-ssu.tistory.com/69

 

C, malloc 시 꼭 형변환 해줘야하는가?

결론 명시적인 것이 좋은건가?에 따라 다름 ( ∴ 안해줘도 됨 ) 이말은 `형변환이 필요없다`는 뜻이 아니고 `할 필요가 없다`는 뜻입니다 우리는 malloc 함수의 리턴값이 void * 라고 배웠습니다. 명시적으로 형변..

untitle-ssu.tistory.com

또한 실수를 줄이기 위해서 한다고 한다.

 

 

포인터* = 포인터*는 무슨의미인가, 또한 선언과 동시에 int 포인터* = 무엇인가?

 

선언과 동시에 초기화하는

int 포인터* = 는 바로 주소를 연결해주는것을뜻함

int *p = 0;의 의미는 0의 주소를 연결해준다는뜻

int *p = 0x11223344; 의 의미는 11223344의 주소에 연결해준다는것을 의미(에러발생)(허용되지않는곳에 접근)

주소를 연결해준다는뜻 예를들어서

 

	int a = 400;
	char c = 5;
	int* numPtr1=NULL;     // int형 포인터 선언
	char* numPtr2 = NULL;
	numPtr2 = &c;
	numPtr1 = &a;
	numPtr1 = numPtr2;

 

포인터 형변환

 

 

그냥 앞에 numPtr2 = (int*)numPtr1; 처럼 명시해주면된다.

정확히 말하면 형을 변환하는게 아니고 실수를 줄이기 알려주는것이다.

 

 

동적할당

 

동적할당을 알기위해서 우선 알아야되는 개념이 heap영역임

힙영역은 사용자가 임의로 만들어주는 영역으로 그냥 프로그래밍을 하다보면 이 영역이 왜필요한가싶다

하지만 메모리를 잘 다뤄야하는 c에서는 필수적인것이 메모리의 낭비적인 측면에서 내가 원하는만큼의 메모리를 가지고 놀수 있어야하는데 이 부분을 만족시켜주기위해서 heap영역이 필요하다.

 

우선

 

void main(int argc,char* argv[]){

    int  i=10;

    int t[i];

    return 0;

}

 

이 왜 컴파일이 되지 않는지부터 알아야하는데

우선 스택 영역에 할당될 메모리의 크기는 컴파일 타임(컴파일 하는 동안)에 결정된다고 되어있다.
배열 선언문의 크기값은 변수로 지정할 수 없고 반드시 상수로만 지정할 수 있기 때문이다

위에 함수가 안되는이유는

i 가 10으로 초기화되었다는사실은 컴파일시에는 모르고 런타임때 알수있기 때문에 컴파일시에는

i의 값이 무엇인지 알수없어서 t[i]에서 오류가 난다. 즉 초기화는 런타임때 알수있기에 t[i]는 불가능하다.

 

그리고 기본적으로 알아야할 운영체제의 메모리 관리 방법

 

16비트 운영체제에서는 응용 프로그램이 임의의 주소 공간을 액세스할 수 있었지만 32비트의 보호된 운영체제들은 안전성을 높이기 위해 응용 프로그램이 임의의 메모리를 액세스하는 것을 금지하고 있다. 반드시 운영체제를 통해서만 메모리를 할당받을 수 있다.

 

sizeof(int),sizeof(int*)

 

int는 무엇이고 int*란 무엇일까?

 

int의 크기는 4

int*는 포인터의 크기이기때문에 즉 주소의 크기이다.

결국에는 int*는

32비트일경우 주소의크기가 4바이트이고

64비트일경우 주소의크기가 8바이트이다.

 

더 쉽게 말해 int*는 주소를 담을 간의 크기를 말한다

32비트에서는 주소를 0x11223344 하여서 총 4바이트가 필요하지만

64비트에서는 주소를 0x1111222233334444 하여서 총 8바이트 필요하다.

 

32비트에서는 char*,int*,short*,double* 전부 4바이트이다.

64비트에서는 char*,int*,short*,double* 전부 8바이트이다.

 

 

 

 

 

1차원 동적할당 2차원 동적할당 3차원 동적할당

 

1차원

#include <stdio.h>
#include <stdlib.h>    // malloc, free 함수가 선언된 헤더 파일

int main()
{
	int a=0;
	printf("size : ");
	scanf_s("%d", &a);
	int* arr;
	arr = (int*)malloc(sizeof(int) * a);

	for (int i = 0; i < a; i++) {
		printf("%p\n", arr + i);
	}
	
}

 

2차원

#include<stdio.h>

int main(int argc,char* argv[]){
	int height = 6, width = 8;
	int **arr;
    arr = (int**) malloc ( sizeof(int*) * height );
	for(int i=0; i<height; i++){
    	arr[i] = (int*) malloc ( sizeof(int) * width );
	}

}

 

 

 

 

NULL 포인터 역참조는 왜 발생하는가

 

대부분의 운영체제에서는 이 널 포인터를 메모리의 첫 페이지 주소 0(0x00000000)로 사용하고 있습니다. 따라서 여기에 접근하면 보통 프로그램이 비정상적으로 종료합니다.

 

예를들어 ) int*p = 0;으로 접근하ㅁ면 종료됨

 

그런데

 

절대다수의 코드에서 Null Pointer Dereference 는 이렇게 눈에 빤히 보이는 코드에서 잘 발생하지 않습니다. 보통 라이브러리를 잘못 다룰 때 많이 쓰이죠. 많은 라이브러리 함수는 사용자가 의도한 값이 없거나, 기타 에러 등의 이유로 널 포인터를 리턴하는 경우가 많습니다. 이 리턴된 값을 확인 없이 바로 무언가를 쓸 때 Null Pointer Dereference 가 종종 발생합니다. 

 

그러니까 메모리0에 접근하는경우보다 함수를 가져다쓸때 return nulll 로 인하여 nullpointer 역참조가 발생한다는것

 

그리고 malloc 함수를 호출 시 메모리가 부족하면 널 포인터가 리턴됩니다.

 

 

여기에서는 malloc으로 메모리를 할당받은줄 알았지만 실제적으로 메모리를 할당받지 않았던것

그래서 결국 null pointer 역참조가 발생하였음

 

이렇게 NULL일경우 return -1을 하면 정상적으로 작동한다.

 

 

MMU란?

 

 

segmentation 그리고 paging은 무엇인가?

 

메모리를 관리하는 기법으로

페이징과 세그멘테이션이 있는데

페이징은 피지컬메모리(실제메모리)에서 분할한 하나의 프레임과 일치시켜서 관리하는것이고

세그멘테이션은 페이징과 달리 서로다른 메모리를 피지컬메모리에 관리하는것이다.(사용자관점)

같은 크기의 세그먼트들은 같이 연속된 공간안에있으며 크기별로 나뉘어져있음(?)

 

세그멘테이션 : 외부단편화

페이징 : 내부단편화

 

 

프로그래머가 알아야 하는 메모리 관리 기법

프로그래머가 알아야 하는 메모리 관리 기법 Sunny Kwak (sunnykwak@daum.net)

www.slideshare.net

 

내부단편화,외부단편화란 무엇인가

 

내부단편화는 메모리가 50M 필요한공간은 30M여서 20M가 낭비되는것

외부단편화는 남은메모리가 50M + 50M인데 나는 70M를 요구할경우 용량이 충분함에도 활용하지못하는것

 

segmentation fault는 왜 발생하는가?

 

  • 이미 메모리 해제 된 변수에 접근할 때
  • read-only로 설정된 메모리 영역에 쓰려고 할 때 등이 있습니다.

그러니까 세그멘테이션으로 나눠놓고 쓰는데 할당 해줬는데 해당 세그멘테이션을 초과한곳을 메모리에서 사용하려고 할때 발생함

 

그렇다면 nullpointer는 무엇인가? 

 

널포인터(Null Pointer)란 아직 할당되지 않는, 어떠한것도 안가르키는 포인터

 

 대부분의 운영체제에서는 이 널 포인터를 메모리의 첫 페이지 주소 0(0x00000000)로 사용하고 있습니다. 

 

-> 널포인터 역참조로 이동

 

 

*,**,***,****의 차이

 

 

 

컴파일(compile)과 런타임(runtime)차이점

 

컴파일은 프로그래머가 작성 소스코드 -> 기계어코드로 변환

런타임은 응용프로그램이 동작되어지는 때

컴파일이 완료되더라도 런타임오류 발생가능

 

 

malloc()

 

a = malloc();

a의 데이터의 주소로 이동하여 그 주소의 데이터를 malloc한 주소값과 연결한다는뜻

malloc()은 heap영역에 메모리를 할당해줌

#include <stdio.h>
#include <stdlib.h>    // malloc, free 함수가 선언된 헤더 파일

int main()
{
	int num1 = 20;    // int형 변수 선언
	int* numPtr1;     // int형 포인터 선언

	numPtr1 = &num1;  // num1의 메모리 주소를 구하여 numPtr에 할당

	int* numPtr2;     // int형 포인터 선언

	numPtr2 = malloc(sizeof(int));    // int의 크기 4바이트만큼 동적 메모리 할당

	printf("%p\n", numPtr1);    // 006BFA60: 변수 num1의 메모리 주소 출력
								// 컴퓨터마다, 실행할 때마다 달라짐

	printf("%p\n", numPtr2);     // 009659F0: 새로 할당된 메모리의 주소 출력
								// 컴퓨터마다, 실행할 때마다 달라짐

	free(numPtr2);    // 동적으로 할당한 메모리 해제

	return 0;

}

int* numPtr2;     // int형 포인터 선언
numPtr2 = malloc(sizeof(int));    // int의 크기 4바이트만큼 동적 메모리 할당

 

malloc의 의미

numPtr2는 스택영역

numPtr2가 가르키는 영역은 힙영역

 

 

 

***이 가르키는건 ****의 주소, **이 가르키는건 ***의 주소
int *arr = (int*)malloc(sizeof(int)*n);

 

**포인터는 *포인터를 매개로 하기에 반드시 필요함

 

 

해석: numPtr2의 주소가 malloc으로 할당한 힙영역의 주소로 연결

 

malloc() 과 free()

int *ptr = (char*)malloc(sizeof(int));

는 포인터처럼 형변환을 하자 실수를 줄이기위해서, 사실 할필요는 없지만

 

qsort()와 cmpfunc()

 

qosrt : 퀵정렬 해주는 함수

cmpfunc : 퀵정렬해줄때 필요한함수

 

qsort( [ 값(ex. 배열) ] , [정렬할곳 예를들면 1,2,3,4,5 중에 3을 입력했다면 1,2,3만 정렬] , [정렬할값의 크기], [비교함수]);

 

cmpfunc(const void* a,const void* b){

        return (*(int*)a - *(int*)b);

}

 

a랑 b를 받은것을 정렬할때 int형으로 정렬하여 정렬하면

약속한것임

  • a < b일 때는 -1을 반환
  • a > b일 때는 1을 반환
  • a == b일 때는 0을 반환

https://dojang.io/mod/page/view.php?id=638

 

C 언어 코딩 도장: 73.2 퀵 정렬 함수 사용하기

이번에는 퀵 정렬 함수를 사용해보겠습니다. 퀵 정렬 함수에는 정렬할 배열 또는 메모리의 주소, 요소 개수, 요소 크기, 비교 함수를 넣어줍니다(stdlib.h 헤더 파일에 선언되어 있습니다). qsort(정렬할배열, 요소개수, 요소크기, 비교함수); qsort(정렬할메모리주소, 요소개수, 요소크기, 비교함수); void qsort(void *_Base, size_t _NumOfElements, size_t _SizeOfElements, int (*_PtF

dojang.io

 

 

memset()

 

#include <stdio.h>
#include <stdlib.h>    // malloc, free 함수가 선언된 헤더 파일
#include <string.h>    // memset 함수가 선언된 헤더 파일

int main()
{
    long long *numPtr = malloc(sizeof(long long));  // long long의 크기 8바이트만큼 동적 메모리 할당

    memset(numPtr, 0x27, 8);    // numPtr이 가리키는 메모리를 8바이트만큼 0x27로 설정

    printf("0x%llx\n", *numPtr);    // 0x2727272727272727: 27이 8개 들어가 있음

    free(numPtr);    // 동적으로 할당한 메모리 해제

    return 0;
}

 

 

 

 

 

기타 잡다한 정보

 

1.

func라는 함수에서 func(n)을 할때

    call by value(콜바이 벨류) = 메모리를 차지함

 

func라는 함수에서 func(&n)을 할때

    call by references(콜바이 리퍼런스) = 메모리를 차지안하지만 원본값이 변경될 위험이있음

 

 

2.

------------------------------------

stack(지역,매개,리턴)

------------------------------------

heap(동적메모리할당, 이 영역접근위해서는 malloc

bss

data(전역,정적,배열,구조체)

code(text)(hex나 bin파일 메모리다)

-------------------------------------

 

3.

포인터 연산 +,-는 크기만큼 더하거나 뺀다.

 

4.

const는 변수를 상수화하기 위해 사용한다.

const int *n;  (*n의값 변경 불가)

int* const n = &b; (포인터 주소변경불가)

 

5. 

int *temp = 1024 해버릴경우에는

실행은 가능하다만(억지로 할경우) 주소를 지정해주지 않았기때문에 쓰레기값으로 들어간다.

이말인즉슨, 알수 없는 이상한 주소에 1024라는 값이 들어가는것이다.

 

실행은 되지 않는다.

 

6.

포인터도 변환할때 자료형이 다르면 컴파일경고 발생함

'Development language > c' 카테고리의 다른 글

[basic] 함수 - Function  (0) 2019.08.29
[basic] 파일  (0) 2019.08.29
[basic] 구조체 포인터 함수 포인터  (0) 2019.08.21
Comments