본문 바로가기
TIL/따배씨

배열과 포인터(2021-01-15 ~ 2021-01-17)

by Dev_Dank 2021. 1. 15.

- (2021-01-17) 현재 chapter 10 마지막까지 들었는데 너무 이해가 가지않고 헷갈리는 부분이 많다(13~16강은 정리할 엄두가 안난다....). 포인터가 어렵다는 말은 들었는데 정말 어렵구나 싶음.  수강진도를 더 나가기전 다음주 다시 들으며 한번더 정리하려 해보자.

-  (2021-01-19) 9강부터 다시 복습을 했는데 어느정도 정리가 된것 같다. 주말아니면 내일 한번더 강의를 전체를 다시 보면 어느정도 더 이해 될 수 있을거 같다.

-  (2021-01-27) 배열의 이름에는 첫번째 원소의 주소가 담겨있다는 사실을 꼭 기억하자. www.inflearn.com/questions/138442

 

10.1 배열과 메모리

배열의 사전적 의미 -> 동일한것이 나열된것

C의 배열의 선언방법과 for에서의 사용법

C의 배열의  기본적인 선언방법과 for에서의 사용법

 

공간만 먼저 선언해두고 그안에 들어갈것은 나중에 선언하는 것도 가능하다.
[]연산자의 우선순위가 &연산자 보다 높기 때문에 &arr[0]과 &(arr[0])은 같다.
배열 안의 첨자는 결국 첫번째 포인터 주소로 부터의 거리를 의미한다.

10.2 배열의 기본적인 사용방법

 

배열을 선언할떄 사용한 배열의 이름은 전체 할당받은 공간의 첫번째 주소(포인터)처럼 쓸수있다. (두개가 완전히 똑같은건아님 -> 추후 강의에서 설명 예정)

실제로 주소로 출력해보면 똑같이 나온다.

10.1강에서 설명된 것 처럼 배열의 자료형을 int(4바이트)로 선언했기때문에 주소를 long으로 변환하여 출력해보면 4씩차이가 나는것을 볼수있다. (이하의 예시 참조)

배열의 숫자를 잘못 써도 빌드에서는 문제가 없고 실제 프로그램을 실행할때 런타임 에서가 발생한다. 
(파이썬과 달리 -1을 쓸수가 없다....)

배열에 const를 사용하면 배열의 원소를 바꿀수 없음

배열의 값을 초기화 하지 않으면 기본적으로쓰레기 값만 들어있는 상태이다. 
(단 x64에서 컴파일하냐 x86에서 컴파일하냐에따라 값이 달라질수 있음) 

부분적으로라도 초기화를 하면 나머지를 0으로 초기화 해준다.

배열의 사이즈 보다 더 많이 집어넣으려 하면 컴파일 에러 발생

배열의 사이즈를 지정하지 않을 경우 컴파일러가 자동으로 원소갯수만큼 사이즈를 지정해줌

사이즈를 지정하지 않았을 경우 for 문에서는 이렇게 배열의 사이즈를 활용 가능하다. (동적 할당에서는 이렇게 안된다 -> 추후 강의 에서 설명예정) 추가로 sizeof는 연산자이며 피연산자가 data type이 아닌경우는  괄호를 쓰지 않아도 된다. 

배열을 초기화 할때 값이 들어갈 자리를 직접 지정 할수도 있다. 

배열사이즈에 가능한 숫자는 이하의 예시 참조

배열사이즈에 수식이나 sizeof 같은 연산자를 써두면 컴파일러가 컴파일할때 미리 계산 해버릴수 있어서 괜찮음. 

VLA도 참고하자.

10.3 포인터의 산술 연산

포인터에 특정 값을 더한다는 것은 포인터의 자료형에 맞추어서 (배열의 다음값을 읽을 수 있도록) 건너뛰어 간다라고 생각하는게 이해가 쉽다. 
(10.1강에서 설명된 배열을 생각해보면 좋음)

포인터를 int(4바이트)로 설정했기 떄문에 포인터에 1을 더하면 4가 더해진다.

 

포인터를 void 로 선언하면 포인터가 얼마나 값을 읽어야 하는지 몰라서 산술연산이 불가능함

포인터 앞에 단항 연산자 +, -를 붙이는 것은 불가능
(포인터는 주소값이기 떄문에 단항연산 +, - 를 하는게 의미가 없음)

포인터 끼리는 더할수 없음
(주소값끼리 더해도 의미 없는 값만 나오기 떄문에 막혀있음. e.g. 철수가 305호 살고 영희가 307호 사는데 305 + 307 = 612임 612는 아무 쓸모 없는 숫자...)

포인터의 뺄샘은 가능하다. 
(아래의 예시는 배열안에서 인덱스 차이가 얼마나 나는가를 의미함. )

double은 8바이트고 arr[3]과 arr[5]는 인덱스 거리차이가 2니까 8바이트 2개인 16만큼의 차이가 난다. 

10.4 포인터와 배열

앞강에서 배열이름은 &배열이름[0]과 동일하게 사용 가능하다고 했음 -> 이하의 예시 처럼  int* ptr = arr; 처럼 쓸수도 있다. 

주소값은 결국 ptr, arr, &arr[0]이 전부 같다는걸 확인 할 수 있다.

주의 할점은 배열이름에 직접 포인터 연산을 할수는 없다. 
(포인터 변수에 대입하고 해당 변수를 이용해야됨)

arr += 2

인덱싱과 포인터 산술연산을 서로 바꿔서 쓸수 있다. 

밑줄친 부분 전부 400으로 출력되는 것을 확인 가능

괄호를 쓸때와 안쓸때 값이 다르다는 것도 주의
(아래의 printf는 dereferencing을 먼저하고 1을 더하고있는 형태임)

후위 연산자를 달면 dereference 먼저하고 이후에 포인터를 더한다.

추가로 기본자료형의 포인터는 [] 연산자를 사용할 수 있다. 이하의 답변이 이해에 도움이 되었다.  

www.inflearn.com/questions/127355

 

포인터 산술연산 질문 - 인프런

질문 - 포인터 산술연산 질문 int main() { int arr[5] = { 1,2,3,4,5 }; int* ptr1, * ptr2, * ptr3; ptr1 = arr; ptr3 = ptr1 + 4;         printf('%p, %d %p\n', ptr3, *ptr3, &ptr3); return 0; } 위 코드에서 ptr3 = ptr1 + 4 라는 점이 제가

www.inflearn.com

10.5 2차원 배열과 메모리

C에서의 2차원 배열 사용방법은 이하와 같다. 

주의 할점은 컴퓨터 메모리는 결국 1차원이므로 2차원 배열을 위의 사진 처럼 선언해도  내부적으로는 이하와 같이 1차원 배열과 같은 도식으로 메모리에 저장된다. 

2차열 배열을 초기화 할때 결국 메모리는 1차원이기 때문에 1차원 처럼 초기화 할수도 있다. 

주석부분에 2차원 배열을 1차원 처럼 초기화.  

첫주소를 포인터변수에 넣고 1차원 배열처럼 사용할 수 있다.(2차원 배열도 결국 위에서 설명한것처럼 내부에서는 1차원 배열로 작동 하기 때문임. *를 쓰지 않았는데 제대로된 값이나오는것도 이후 동적할당에서 추가 설명 예정 )

첫주소  &arr[0][0]을 *ptr에 넣고 1차원 배열 처럼 활용하고 있다. 

sizeof로 크기를 확인 해볼수 있다. 

arr[0]의 값은 {1, 2, 3}이 전부 포함된 크기로 출력됨.

배열과 포인터에 대한 더 자세한 설명은 이하의 링크를 참조 해도 좋다.
www.programiz.com/c-programming/c-pointers-arrays

10.7 배열을 함수에게 전달해주는 방법

배열을 함수에 전달 해주면 사실상 배열의 첫번째 주소만 들어있는 포인터만 넘어가게된다. 
(배열은 큰 사이즈를 가지고 있을텐데 그때마다 모든 데이터를 복사해서 함수에 넘겨주면 비효율 적이기 때문)

arr1과 arr2모두 average 함수안에서 크기는 4로 표시되는 것을 볼 수 있다. (즉 함수의 인자로 넘어온 arr은 포인터인것)

위의 예시에서 알수있듯이 즉 함수에 인자로 배열이 넘어가게되면 배열 그자체가 넘어가는게 아니라 맨첫번째 주소(포인터)만 넘어 가게 되는 것이다. 이번 강의 초반에서 설명된 부분을 잘 생각해보자. 

배열을 넘겨 받는 함수의 프로토타입과 정의선언은 주로 이하와 같이 하는 편이며 주석에 작성한 것과 같이 프로토타입을 선언할 수도 있다. 추가로 배열의 사이즈는 int n 으로 따로 빼낸것을 주목하자.

10.8 두 개의 포인터로 배열을 함수에게 전달해주는 방법

이하와 같은 패턴으로도 함수에 배열을 전달 할 수 있습니다.

10.9 포인터 연산 총정리

printf로 포인터의 차이를 출력할때는 아래와 같이 t를 붙인다

포인터 자체도 변수라는점에 주의 하자. 

포인터도 결국엔 변수이기 때문에 자료형을 바꿀수(캐스팅) 있다.

위의 예시에서 서로다른 자료형의 포인터를 비교하기위해 ptr_d의 자료형을 변환하고있다. 
자료형을 변환 하지 않을 경우 빌드는 정상적으로 되지만(비주얼스튜디오 output 창에서 warning은 뜸) 실제 프로그램 실행시 결과값이 제대로 나오지 않을 가능성이 높다. 

10.10 const와 배열과 포인터

const, volatile 등을 type qualifiers 라고 부른다.

기본적인 const의 용도는 이하와 같다.
(값이 바뀌게되면 안되는 것을 확실히 하기 위해 사용)

배열명을 포인터에 넣어주면 포인터를 배열처럼 사용가능한것을 기억하자. 

배열 선언할때 const를 선언하더라도 포인터를 활용할때는 주의해야한다.   

const double* pd 로 const를 선언하면 *pd = 3.0; 와  pd[2] = 1024.0; 는 빨간줄이뜨지만(=문제발생) pd++;에는 빨간줄이 안뜬다(=문제가없다.) 만약 pd++;까지 막으려면 이하의 사진처럼 해야한다. 
const double* const pd = arr2; 처럼 선언해야 pd++;이 막히는 모습.

이하의 링크의 내용이 이해에 도움이 된다.

www.tutorialspoint.com/difference-between-const-int-const-int-const-and-int-const-in-c

 

Difference between const int*, const int * const, and int const * in C

Difference between const int*, const int * const, and int const * in C Pointer In C programming language, *p represents the value stored in a pointer and p represents the address of the value, is referred as a pointer. const int* and int const* says that t

www.tutorialspoint.com

왼쪽에서부터 읽어나가면 이해가 쉽다.

10.11 배열 매개변수와 const

함수에 매겨변수로 배열을 넘길떄 배열의 값이 바뀌질 원치 않는다면 이하와 같이 함수의 파라미터에 const를 작성해주는 것이 좋다. 

아니면 main 함수에 존재하는 배열에 const 를 작성해도 된다. 

실행은 정상적으로 됬으나 add_value 함수가 실행되서 output 창에서 warning 이 뜨는 것을 볼수 잇다. 

10.12 포인터에 대한 포인터(2중 포인터)의 작동 원리

포인터는 주소값을 저장하는 변수였다. 

이중포인터는 포인터 변수의 주소를 저장할수 있다. 

기본적인 선언법은 이하와 같다.

내부적으로 일반 변수도 자기가 사용할수 있는 메모리 공간 첫 번쨰 주소를 알고 있고 얼마나 많은 메모리를 사용 할수 있는지는 자료형에 의해 결정됨. 

10.13 포인터의 배열과 2차원 배열

이차원 배열도 결국 내부적으로는 1차원으로 저장되어있는 형태이다. 
&parr[0]는 parr 포인터 자체의 주소를 나타내고 있기때문에 이하의 경우들과는 다른 메모리 주소가 출력되는점에 주의 
char* name[] = { "Aladdin", "Jasmine", "Magic Carpet", "Genie" }; 과 문자열을 원소로 하는 포인터의 배열을 선언하면 각 문자열의 첫번째 글자의 위치가 원소로 담기게 된다. 위의 예시를 설명하자면 char타입이 담기는 name이라는 포인터의 배열의 원소는 각 문자열의 첫번째 글자가 저장된 위치가 저장된다(A가 저장된위치, J가 저장된 위치, M이 저장된 위치, G가 저장된위치.)

10.14 2차원 배열과 포인터

기본적인 개념도는 이하와 같다. 

지난 강의에서 배열의 이름은 포인터처럼 첫번째 원소의 주소를 나타낸다고 했었다. 예를들어 int arr[2] = {1, 2}; 가 있다면 arr부분 까지만 쓰면 arr 배열의 첫번째 원소의 주소가 되는것.

이차원 배열도 마찬가지의 논리이다. float arr2d[2][4] = { {1.0f, 2.0f, 3.0f, 4.0f},{5.0f, 6.0f, 7.0f, 8.0f} };  이라는 이차원 배열에서 이름이 되는 부분은 arr2d[2]부분까지가 arr2d[2][4] 라는 전체 배열의 이름이 될수 있는것이기 때문에 arr2d[2]부분까지를 포인터 처럼 쓸수 있는 것이다. 자세한 예시는 이하의 사진을 참고하자. 

 

pa는 단일포인터고 ap는 포인터의 배열이기때문에 주소를 출력해보면 다르게 나온다. (=서로 다른 메모리 공간에 위치해있다.)

 

10.15 포인터의 호환성

다른자료형의 포인터끼리 대입하면 warning 발생가능

int ar1[2][3] = {3, } 처럼 초기화 하면 이하의 사진과 같이 초기화 된다. 

 

 

 

10.16 다차원 배열을 함수에게 전달해주는 방법

이하의 예시에서 프로토타입과 실제 함수선언 그리고 함수를 호출하여 사용할떄 인자로 어떻게 넘겨주는지 까지 잘 봐두자.

10.17 변수로 길이를 정할 수 있는 배열

비주얼 스튜디오에서는 배열의 길이를 변수로 지원하지 않는다. 

10.18 복합 리터럴(Compound Literal)과 배열 ​

배열을 리터럴 상수처럼 선언하여 사용할수 있다.

위의 예시에서  int b[2] = {3,4}; 와같이 원소가 2개인 배열을 선언할때 대입을 통해 선언하는 것이 아니라 (int[2]){3,4} 처럼 선언하고있다. 즉 윗줄의 3; 3.14; 처럼 단순히 상수를 쓰는 것 처럼 배열을 선언하고 있음.

참고로 int c[2] = (int[2]){3,4}; 처럼 복합리터럴을 통해 배열을 초기화 하는것은 문법으로 막혀있음. 

더 자세한 설명은 링크참조 en.cppreference.com/w/c/language/compound_literal

 

compound literals - cppreference.com

Constructs an unnamed object of specified type in-place, used when a variable of array, struct, or union type would be needed only once. [edit] Syntax ( type ) { initializer-list } (since C99) where type - a type name specifying any complete object type or

en.cppreference.com

복합리터럴은 이하와 같이 사용할 수 있다. 

함수의 인자로 바로 넣어주거나 포인터에 대입후 활용도 가능. 

 

댓글