반응형
728x90
반응형

함수 포인터와 void 포인터

  • 함수 포인터

함수 또한 메모리 상에 바이너리 형태로 올라가 호출 시 실행이 되는데, 메모리 상에 저장된 함수의 주소 값을 저장하는 포인터 변수가 함수 포인터 변수이다.

배열의 이름이 배열의 시작 주소 값을 의미하듯이 함수의 이름 또한 함수가 저장된 메모리 공간의 시작 주소 값을 의미한다.

함수 이름의 포인터 형은 반환 타입과 매개변수의 선언을 통해 결정 짓도록 약속. , 반환 타입과 매개변수의 선언이 일치하는 모든 함수들의 포인터 형은 모두 같다.

9번 라인과 10번 라인에서 볼 수 있듯이

반환 타입 (*포인터변수이름) (매개 변수) = 함수 이름

다음과 같은 형태로 함수 포인터 변수를 선언하고 함수의 주소를 받아서 12, 13번 라인처럼 함수 포인터 변수를 통한 함수 호출이 가능하다.

  • Void 형 포인터 ( 자료 형이 없는 포인터 )

다음과 같이 선언되는 포인터를 void 형 포인터 변수라고 한다.

Void 형 포인터는 어떤 변수의 주소 값이든 함수의 주소 값이든 상관 없이 주소값의 경우는 무엇이든 전부 담을 수 있다. 다음 예는 void * 변수에 다양한 주소 값을 저장하는 예를 나타낸 것이다.

, void 형 포인터 p를 이용하여 참조를 하는 순간(=포인터 연산을 하는 순간) 컴파일 에러가 발생한다. 왜냐하면 void * 포인터는 포인터 연산 시 증감의 크기에 대한 정보가 단 한 부분도 없다. , 해당 주소로부터 어느 범위까지 연산을 하여 참조를 해야하는지에 대한 정보가 없기 때문에 참조(포인터 연산)가 불가능하다.

void형 포인터는 주소 값을 저장하는 것에만 의미를 두고 포인터 형은 나중에 결정한 참조하는 경우에 유용하게 사용된다 -> 동적 메모리 할당

  • Main 함수를 통한 인자 전달

위의 예제에서 argc는 파일 이름을 포함한 전달 되어온 인자의 수를 나타내고, argv는 더블 포인터 형태로 싱글 포인터 혹은 싱글 포인터 배열의 각 요소를 가리킨다. , argv는 두 번째 결과에서 “./t”, “yoo”, “seung”, “ho”의 주소 값인 char * 타입을 가리키기 때문에 더블 포인터 형태이다.

728x90
반응형

'컴퓨터 언어 정리 > C 언어' 카테고리의 다른 글

16 구조체  (0) 2020.10.12
15 문자와 문자열  (0) 2020.09.29
13 다차원배열 및 포인터의 관계  (0) 2020.09.21
12 포인터의 포인터  (0) 2020.09.15
11 다차원 배열  (0) 2020.09.14
728x90
반응형

포인터와 함수

  • 값의 복사 인자 전달 방식 ( 기본 인자 전달 방법 )

먼저, 기본적으로 함수에서 인자를 전달할 때는 값의 복사 방법으로 인자가 매개변수로 전달이 된다.

위의 예제에서 num 변수를 func 함수의 인자로 넣어 값을 전달하는데 이 때 전달되는 것은 num 변수가 아니라 num 변수의 값이 전달되어 arg 변수에 저장된 것이다. 결국, 지역 변수 num과 함수의 매개 변수 arg는 서로 전혀 관련 없는 별개의 변수이다. ( arg 매개 변수 값을 아무리 변경하더라도 num 지역 변수의 값의 변화는 일어나지 않는다. )

 

  • 배열을 함수의 인자로 전달하는 방법

배열을 기본 인자 전달 방법인 값의 복사 방법으로 통째로 복사하여 전달하는 방법은 존재하지 않는다.

위와 같은 소스는 컴파일 가능하지 않다. 왜냐하면 매개 변수로 배열을 선언하는 것은 허용되지 않기 때문이다. 그러나 gcc 컴파일러 6.4.0 version을 기준으로 위의 소스가 실행이 되고 정확한 결과가 나와서 가능한 것처럼 보였으나, 

위와 같이 지역 변수의 배열과 매개 변수의 배열의 배열 이름의 값을 출력해 보았다. 그 결과는 다음과 같다.

두 배열 이름이 같은 주소를 지니고 있다. 이 뜻은 지역 변수의 배열 arr는 문제 없이 정상적인 방법으로 메모리 할당이 이루어져 배열을 이루고 있는 것이지만, 매개 변수의 배열 arg는 외형과 다르게 포인터 변수로서 선언되어 있다.

포인터 변수이기 때문에 arg의 값의 변경이 가능하다. 그리고 다음은 그 결과이다.

아파트를 보고 싶어 하는 사람 앞에 아파트를 통째로 복사해 놓을 수 없다면, 아파트를 직접 찾아가도록 주소를 알려주듯이 배열을 통째로 복사가 불가능하다면 배열의 시작 주소 값을 전달하고, 그에 맞는 포인터 형을 매개 변수를 이용하여 포인터 연산으로 각각의 배열 요소에 접근하여 원하는 값을 참조하면 된다.

18번 라인에서 넘겨주는 배열 이름(배열의 시작 주소)5번 라인에서 포인터 변수를 통하여 받고 포인터 연산을 통하여 ( arg[i] = *(arg+i) ) 각각의 요소를 참조하여 출력을 한다. 매개 변수 한정으로 5번 라인에서 주석 친 부분과 같은 형식으로 매개 변수를 선언하고 주소를 받을 수 있다.

  • Call by value 그리고 Call by reference

두 방법에 대해 정리하기 이전에 두 변수의 값을 서로 교환하는 “SWAP” 문제에 대해서 두 방법을 적용해보고 해결하는 과정으로 두 방법을 정리할 것임.

먼저 call by value이다. 이 방법은 함수 인자 전달의 기본 방식이다.

 swap 함수 내에서는 값이 변경되었으나, 정작 바뀌어야 할 main 함수 내의 변수들은 swap이 이루어지지 않았다 이는 복사에 의한 인자 전달이기 때문에 변경이 되지 않는 것은 당연한 일이다. 변경된 것은 swap 함수 내의 매개 변수끼리 값이 변경된 것이다. 이러한 swap은 진정한 의미의 swap이라고 할 수 없다. 이를 해결하기 위해선 주소가 주어지고 해당 주소를 직접 참조하여 값을 변경하는 방식으로 전개를 해야 한다.

다음은 call by reference이다.

주소를 전달하고, 전달된 주소를 참조하여 값을 변경하는 방법으로 값이 성공적으로 swap 되었음을 확인할 수 있다. scanf 함수 또한 call by reference 방법으로 인자를 전달하는 형태이고, 문자열(%s)을 입력 받을 때 ‘&’ 를 붙이지 않는 이유와 그 외의 값을 받을 때 ‘&’ 를 붙이는 이유를 call by reference 방법으로부터 알아낼 수 있다.

 

  • 포인터에서의 const 키워드

다음과 같은 변수가 선언되어 있는 상황이다.

이러한 상황에서 const 키워드를 어느 위치에 사용하는지에 따른 결과들을 분석할 것.

1.     포인터 변수가 참조하는 대상을 상수화(const )한다.

포인터 변수 p를 통하여 number의 값이 변경되는 것을 허용하지 않는다.

2.     포인터 변수를 상수화(const )한다.

포인터 변수 p를 상수화 하여 포인터 변수 p의 값이 변경되는 것을 허용하지 않는다.

3.     둘 다 상수화(const )한다.

p를 통한 number의 값의 변경, p의 값 변경 모두 허용하지 않는다.

4.     일반 변수를 상수화(const ) 한다.

변수 n을 상수화 하여 n의 값을 변경할 수 없다.

Const 키워드는 C++에서만 제공했던 키워드, 표준을 재정립하는 과정에서 C언어서도 지원을 해준다. Const 키워드는 값이 바뀌어서는 안 되는 변수의 변경을 막아준다. 이러한 경우는 프로그램을 실행하는데 있어서는 치명적인 오류로 작용할 수 있지만 문법 상으로는 문제가 없기 때문에 컴파일 오류 하나 받아볼 수 없다. 그렇기 때문에 효율적이고 현명한 프로그램을 원한다면 const 키워드를 자주 사용하는 습관을 들여놓아야 함.

728x90
반응형

'컴퓨터 언어 정리 > C 언어' 카테고리의 다른 글

12 포인터의 포인터  (0) 2020.09.15
11 다차원 배열  (0) 2020.09.14
09 포인터와 배열  (0) 2020.09.12
08 포인터 기본  (0) 2020.09.11
07 1차원 배열  (0) 2020.09.11
728x90
반응형

포인터와 배열

l 배열 이름과 포인터의 관계

다음은 배열의 이름이 무엇인지를 나타내는 예제이다.

위의 소스에서 확인할 수 있는 부분은 배열의 이름 그 자체를 출력했을 때의 값과 배열의 [0]번째 요소의 주소 값이 같다는 점이다. , 배열의 이름은 해당 배열의 시작 주소 값을 의미한다.

결국 배열의 이름 또한 주소 값을 지니고 있다는 관점에서 포인터와 그 개념이 매우 유사하다. 그렇다면 포인터 변수처럼 배열의 이름 또한 주소를 대입해볼 수 없을까? 배열의 이름에 주소 값을 대입하는 예제이다.

8번 라인에 새로운 변수 number를 선언하고, 10번 라인에서 배열의 이름에 number의 주소 값을 넣었다. 왜 배열의 주소가 아니라 일반 변수의 주소를 넣었느냐? 왜냐하면 배열의 이름은 배열의 시작 주소하나를 저장하기 때문에 배열 이름의 입장에서 보면 어떤 하나의 변수의 주소 값을 대입한 것과 다를 바 없이 보이기 때문이다. 그렇기 때문에 배열의 이름에 하나의 변수 number의 주소 값이 대입되는 것은 문제가 되지 않는다. 이렇게 배열의 이름에 변수의 주소를 넣은 결과, 10번 라인에서 배열 타입의 표현 식에 할당하는 부분에서 에러가 발생한다. 그 이유는 배열의 이름은 주소 값을 저장하고 가리키지만 처음 정해진 배열 이름의 값은 변경이 불가능하다. , 배열의 이름은 상수 값이다. 그도 그럴 것이 특별하게 배열을 선언하여 만들었는데 그 배열의 이름을 대표하는 배열의 이름이 가리키는 요소를 마음대로 바꾸게 되면 무엇을 위해 배열 이름을 지정해가며 배열을 생성했겠는가?

다음은 위에서 배열과 포인터의 관계에 대해 내린 결론을 정리하여 나타낸 표이다.

배열 이름은 포인터 변수와 같은 특성들을 지니고 있지만 상수라는 차이점이 있다. 그래서 배열 이름을 포인터 상수라고도 표현한다. 포인터 변수와 배열 이름은 변수이냐, 상수이냐 의 차이점 밖에 없기 때문에 그 차이를 제외하면 포인터 변수와 배열 이름은 완전히 같다. 그렇기 때문에 다음의 예제가 가능하다.

10번 라인에서 배열 이름(포인터 상수)의 값을 포인터 변수에 대입시키고 있다. 이것이 가능한 이유는

1.     포인터 변수에 포인터 상수를 대입 -> 전혀 문제될 것이 없다.

2.     대입 연산자를 기준으로 l-valuer-value의 형(type)이 같아야 한다.
-> l-value : int * / r-value : int * == int
(시작) 주소 로 문제될 것이 없다.

배열 이름의 포인터 형은 배열의 이름이 가리키는 대상을 기준으로 결정되기 때문에 r-value 배열 이름의 자료형은 정수형 변수 공간의 시작 주소이므로 int * 가 맞다.

포인터 변수에 포인터 상수를 대입하게 되면 포인터 변수를 이용하여 배열 이름처럼 인덱스로써 접근하는 방법으로 각 요소에 접근을 할 수 있다. , 포인터 변수를 배열의 이름처럼 사용할 수 있다는 것이 결론.

그렇다면 그 반대로 배열 이름(포인터 상수)을 가지고 포인터 연산을 할 수 있을까? 다음은 이에 관한 예제이다.

11번 라인에서 배열 이름과 ‘ * ‘, 애스터리스크 연산자를 이용한 포인터 연산이 진행되고, 12번 라인에서는 배열 이름과 인덱스를 이용한 배열의 0번째 요소에 접근하여 출력을 한다. 11번 라인에서는 배열 이름이 가지고 있는 값 즉, 배열의 시작 주소에 접근하여 해당 값을 출력하기 때문에 0번째 요소를 출력한다.

  • 포인터 연산

포인터를 이용하여 참조 뿐만 아니라 증감 연산이 가능하다. 다만 일반 수를 이용한 연산에 비해서 연산이 많이 제한 된다.  -> 가능한 연산자 수의 제한

포인터 연산에서 중요한 포인트

1.     덧셈, 뺄셈, 참조 연산만 가능하다.
->
곱셈, 나눗셈 등의 연산을 불가능하다.

2.     덧셈, 뺄셈 연산은 일반 수를 통한 덧셈, 뺄셈 연산과는 다르다.
->
포인터 연산에서의 +1과 수에 대한 +1에서 1은 각각 의미가 다르다.

다음은 포인터에 + 연산자를 이용하여 1씩 더하는 예제이다.

int, char, double 자료형 별로 배열을 선언하고, 각각에 대한 포인터 변수를 선언하여 각 배열의 첫 시작 주소(배열 이름)를 대입시킨 상황에서 각각 포인터 연산을 진행하는 예제이다.

먼저, 자료형에 상관없이 배열 이름과 인덱스를 이용하여 요소별 주소 값을 출력한 결과와 포인터 연산에 대한 결과를 출력한 결과가 모두 동일함을 알 수 있다. 그렇다면 도대체 + 연산자를 이용하여 포인터 변수를 포인터 연산한 것이 어떤 의미가 있길래 각 요소의 주소를 출력한 결과와 같은 결과인가?

 

1.     A의 결과에서 pArrpArr+1 값의 차이는 4이다.

2.     B의 결과에서 pCrrpCrr+1 값의 차이는 1이다.

3.     C의 결과에서 pDrrpDrr+1 값의 차이는 8이다.

 

단순히 포인터 변수에 +1 연산을 했을 뿐인데 그 증가의 차이는 자료형 별로 다른 결과가 나타났다. 포인터 연산은 피연산자로 오는 그 대상이 메모리 주소인 만큼 메모리 주소에 대한 연산이 이루어지는데 이 메모리는 무작정 작은 단위인 1바이트 별로 보는 것은 의미가 없다. int 형의 경우에는 4바이트 전부 참조해야 비로소 값이 의미가 있듯이 자료형 별로 최소 참조 바이트가 존재한다. 그렇기 때문에 주소에 대한 연산인 포인터 연산은 자료형 별 최소 참조 바이트를 반영하여 증감을 진행한다. 각 자료형 별로 포인터 연산에 대해서 1의 의미가 모두 다른 이유가 바로 이 때문이다. 1의 증감은 최소 참조 바이트 한 블록을 증감한다는 의미가 되기 때문이다.

포인터 연산에서 1의 의미는 sizeof(해당 자료형)*1 이 된다.
포인터 연산에서 2의 의미는 sizeof(해당 자료형)*2 이 된다.

포인터 연산에서 n의 의미는 sizeof(해당 자료형)*n 이 된다.

그렇다면 다음의 예는 무엇을 의미하는가?

*(p + 1)
p는 임의의 변수를 가리키고 있는 포인터 변수

이는 다음과 같이 해석할 수 있다.

P가 가리키고 있는 주소에 sizeof(해당 자료형) * 1만큼을 더한 주소를 참조(*)한다.

 다음은 그 예제이다.

위와 같은 포인터 연산에서의 성질과 예제를 통해서 알 수 있는 결론은 다음과 같다.

배열 이름[i] == *(배열 이름 + i)
혹은
포인터 변수[i] == *(포인터 변수 + i)

완전히 같다.

  • 문자열과 포인터

c언어에서 문자열을 표현할 때 두 가지 방법으로 표현한다.

1.     배열을 통한 문자열의 저장

2.     포인터 변수를 통한 문자열의 저장

먼저, 첫 번째 방법인 배열을 통한 문자열의 저장이다. 이 경우에는 str 배열의 길이가 자동 계산되어 각 인덱스 별로 문자를 하나씩 저장하여 해당 문자열의 끝에 ‘\0’을 저장함으로써 문자열의 저장이 비로소 완료되는 형태이다.

그러나 두 번째 방법인 포인터 변수를 통한 문자열의 저장의 경우이다.  c언어 프로그램 상의 모든 상수는 CPU를 이용한 연산의 대상이 되기 위해서는 해당 상수가 메모리 상에 적재되어야 한다. “hello, my name is yoo seung ho!” 문자열은 상수로서 메모리에 적재되고, 그 시작 주소인 ‘h’의 주소 값이 반환되어 pStr 포인터 변수에 대입이 된다.

대입 연산자를 기준으로 l-valuechar * 형이고, r-value는 문자열의 시작 주소 값 즉, char * 이기 때문에 대입 연산에는 아무런 문제가 없다.

각 두 경우는 위 그림의 왼쪽과 오른쪽 형태로 동작한다. 배열의 이름의 경우 포인터 상수이기 때문에 가리키는 타겟 문자열을 변경할 수는 없지만, 변수 형태, 배열에 값이 저장되어 있기 때문에 대상이 되는 문자열의 내용에 대해서는 변경이 가능하다. 포인터의 경우 포인터 변수이기 때문에 가리키는 타겟 문자열을 변경할 수 있다. 그러나 그 대상은 상수 형태의 문자열이기 때문에 대상이 되는 문자열의 내용에 대해서는 변경이 불가능하다.

사실은 아래와 같이 되는 것이다.

  • 포인터 배열

말 그대로 포인터 변수들로 배열을 이룬 형태를 말한다.

 

 

728x90
반응형

'컴퓨터 언어 정리 > C 언어' 카테고리의 다른 글

11 다차원 배열  (0) 2020.09.14
10 포인터와 함수  (0) 2020.09.13
08 포인터 기본  (0) 2020.09.11
07 1차원 배열  (0) 2020.09.11
06 함수와 변수의 생명주기  (0) 2020.09.10
728x90
반응형

포인터 기본

  • 포인터

포인터란 변수 형태의 포인터와 상수 형태의 포인터를 어우르는 표현

  • 포인터 변수

포인터 변수는 주소 값의 저장을 목적으로 선언되는 변수이다. c언어의 low 레벨 특성을 나타내는 요소이고, 이러한 포인터를 이용하여 메모리에 직접 접근이 가능하다.

  • c언어에서의 주소 표현

c언어에서는 메모리 상의 주소를 표현할 때 시작 번지만을 가지고 위치를 표현한다. 왜냐하면 각 자료형마다 지니는 바이트 값을 시작 주소로부터 더하여 계산하면 해당 변수의 끝 번지를 계산할 수 있기 때문이다.

  • 포인터 변수의 선언과 & 연산자

다음은 포인터 변수의 선언과 & 연산자의 활용 예를 나타낸다.

위의 예에서 시사해야 할 점은 두 가지이다. 첫 번째는 포인터 변수의 선언 및 사용이고 두 번째는 포인터 변수와 일반 변수의 크기의 차이이다.

먼저 포인터 변수의 선언 및 사용에 관한 이야기이다.

7번 라인에서 일반 변수를 선언하였고, 8번 라인에서는 * (asterisk) 를 이용하여 ‘ int * ‘ (정수형 포인터 타입) 형 변수 pNum을 선언하고 num시작 주소 값을 대입한다. (char, int, double 등과 같이 char *, int *, double * 등 또한 새로운 자료형 타입으로 인정해야한다.) 그리고 12번 라인에서 pNumnum 변수의 주소 값을 출력해 보았을 때 결과가 같은 결과로 나왔는데 이는 당연한 것이다. pNum에는 num시작 주소 값을 대입했기 때문이다. 이처럼 포인터 변수인 pNum은 일반 변수의 주소 값을 저장한다! 그리고 13번 라인에서 * 연산자를 이용하여 pNum이 지니고 있는 주소 값으로 직접 찾아가 직접 접근을 하여 직접 접근한 메모리에 들어 있는 값을 출력했다. 그렇기 때문에 num과 마찬가지로 출력 값이 100으로 같은 결과를 보였다. , *(Asterisk) 연산자는 포인터 변수가 저장하고 있는 주소에 접근 혹은 참조(= 포인터가 가리키고 있는 메모리에 접근 혹은 참조)하는 연산을 수행한다.

다음으로는 포인터 변수와 일반 변수의 크기 차이이다.

12번 라인에 해당하는 결과 num4, pNum8이 나왔는데 int라는 유사점이 있는 변수인데 이렇게 다른 결과가 표출되는 이유로는 포인터는 주소를 표현하는 변수이기 때문에 컴퓨터 시스템이 표현할 수 있는 메모리의 크기 범위와 밀접한 연관이 있다. 64비트 시스템에서는 2^64 만큼의 메모리 번지 표현 경우의 수가 존재하기 때문에 포인터 변수는 자연스럽게 8바이트(=64비트)를 나타내는 것이다. 포인터 변수의 크기는 자료형에 상관없이 컴퓨터 시스템의 메모리 범위에 따라 4바이트(32비트 시스템) 혹은 8바이트(64비트 시스템)로 표현된다.

그렇다면 포인터 변수에서 자료형을 어떻게 일치시켜서 대입해야 하는지, 자료형은 왜 필요한지에 대해 알아보자. 다음은 포인터 변수에 일반 변수의 주소 값을 대입 연산 시 일치시켜야 할 타입들에 대한 정리 표이다.

먼저, 자료형을 어떻게 일치시켜서 대입해야 하는지에 대한 정리이다.

어차피 포인터 변수의 크기는 다 똑같은데 포인터 변수에 타입이 필요한 이유가 무엇인가? 그 이유는 해당 포인터 변수에 저장된 시작 주소로부터 몇 바이트를 읽어 들여야 하고, 그 읽어 들인 값을 정수로 해석해야 할지, 실수로 해석해야 할지를 결정한다. , 포인터의 타입()은 메모리 공간을 참조하는 기준이 된다.

int a = 10;
int * p = &a;
printf(“ %d “, *p); //
이 문장에서 p를 참조할 때 int * 는 참조의 기준이 된다.

  • 포인터의 잘못된 사용과 널 포인터

잘못된 초기화

int * ptr = 1234; // 1234 번지로 들어가게 된다.
*ptr = 100; // 1234
번지는 메모리 상에 중요한 프로그램이 적재되어 있을 수 있는데 해당 공간의 값을 변경 -> 에러

초기화 할 경우에는 NULL 포인터로 초기화를 한다. 아무 곳도 가리키지 않음을 의미한다. ( 0번지 의미가 아님 )

int * ptr = NULL; // 아무 곳도 가리키지 않는 포인터.


728x90
반응형

'컴퓨터 언어 정리 > C 언어' 카테고리의 다른 글

10 포인터와 함수  (0) 2020.09.13
09 포인터와 배열  (0) 2020.09.12
07 1차원 배열  (0) 2020.09.11
06 함수와 변수의 생명주기  (0) 2020.09.10
05 반복과 분기  (0) 2020.09.08

+ Recent posts