포인터의 기본 개념
- 포인터: 메모리 주소를 저장하는 변수입니다.
- * (간접 참조 연산자): 포인터가 가리키는 주소의 값을 가져옵니다.
- & (주소 연산자): 변수의 메모리 주소를 가져옵니다.
코드 설명
- ChangeDouble 함수
- int* p: p는 int형 포인터입니다. 즉, p는 int형 변수의 주소를 가리킵니다.
- p = *p * *p;: 포인터 p가 가리키는 메모리 주소의 값을 제곱합니다. 여기서 p는 포인터가 가리키는 변수의 실제 값입니다.
void ChangeDouble(int* p) { *p = *p * *p; }
- main 함수
- scanf("%d", &num);: 사용자가 입력한 값을 변수 num에 저장합니다. 여기서 &num은 num 변수의 메모리 주소를 의미합니다.
- ChangeDouble(&num);: ChangeDouble 함수에 num 변수의 주소를 전달합니다. 즉, &num은 num의 주소를 전달합니다.
- ChangeDouble 함수에서 p = *p * *p;가 실행되면서, num 변수의 값이 제곱됩니다.
int main() {
int num;
printf("숫자를 입력하시오 : ");
scanf("%d", &num); ChangeDouble(&num);
printf("제곱 : %d\\n", num);
return 0;
}
정리
- &: 변수의 주소를 얻기 위해 사용합니다.
- *: 포인터가 가리키는 주소의 값을 참조하거나 수정하기 위해 사용합니다.
Q.3을 입력했을 때 changeDouble에 3이 들어가는게 아니고 3의 메모리 주소를 가져간거인가?
A.ChangeDouble 함수에는 3 자체가 아니라 3이 저장된 메모리 주소가 전달됩니다. 이 과정을 다시 설명하자면:
- num에 3을 입력하면, num이라는 변수에 3이라는 값이 저장됩니다. 예를 들어, num이 메모리의 0x1000이라는 주소에 저장되어 있다고 가정할 수 있습니다.
- ChangeDouble(&num)을 호출할 때, &num은 num의 메모리 주소(예를 들어 0x1000)를 전달합니다. 따라서 ChangeDouble 함수는 num 변수가 저장된 메모리 위치를 알게 됩니다.
- ChangeDouble 함수의 매개변수는 포인터(int* p)입니다. 이 포인터 p는 num의 메모리 주소를 가리키고 있으므로, p는 num의 값을 참조할 수 있습니다.
- p = *p * *p;에서 p는 p가 가리키는 메모리 주소에 있는 값을 의미하므로, 이는 num의 값을 의미합니다. 따라서 num의 값이 3 * 3 = 9로 변경됩니다.
즉, ChangeDouble에 들어가는 것은 3 자체가 아닌 num의 주소이며, 그 주소를 통해 함수 내부에서 num의 실제 값을 제곱하고 변경할 수 있는 것입니다.
Q.리턴을 안해줘도 ChangeDouble(&num);이것만으로 3을 입력했을 때 어떻게 *P값이 나와?
A.ChangeDouble(&num);만으로도 num의 값이 변경되는 이유는 포인터의 특성 덕분입니다. 이 과정에서 중요한 것은, 함수가 직접 메모리 주소에 접근해서 값을 수정한다는 점입니다.
함수를 호출할 때 일반적으로 값만 복사되어 전달되는데, 포인터를 사용하면 원본 데이터를 직접 수정할 수 있습니다. 이 과정을 단계별로 설명드릴게요.
기본 개념: 값 복사 vs. 주소 전달
- 값 복사: 함수에 변수를 넘기면 그 변수의 값만 복사됩니다. 원래 변수는 바뀌지 않습니다.
- 주소 전달: 포인터를 사용해 변수의 주소를 넘기면, 함수에서 원래 변수의 값을 수정할 수 있습니다.
ChangeDouble 함수가 원본 값을 바꾸는 이유
- ChangeDouble(&num)에서 &num은 num의 메모리 주소를 나타냅니다.
- 예를 들어, num의 주소가 0x1000이라고 가정하면, &num은 0x1000입니다.
- ChangeDouble 함수는 이 주소를 받아서, 그 주소를 가리키는 포인터 p로 처리합니다.
- p는 num의 주소를 가리키므로, p는 num이 저장된 메모리 위치의 값을 나타냅니다.
- p = *p * *p;는 p가 가리키는 값, 즉 num의 값을 제곱하는 연산을 수행합니다.
- void ChangeDouble(int* p) { *p = *p * *p; }
- 따라서 ChangeDouble 함수는 num 변수의 실제 메모리 위치에 접근하여 그 값을 직접 수정하게 됩니다.
리턴이 필요 없는 이유
일반적인 함수는 값을 반환할 때 리턴을 사용하지만, 여기서는 포인터를 사용하여 변수의 메모리 주소에 접근해 값을 변경하기 때문에 리턴 값이 필요 없습니다. 함수가 종료되어도, 수정된 값은 메모리에 그대로 남아있으므로 main 함수에서 num의 값이 바뀐 것을 볼 수 있습니다.
간단한 예시:
void ChangeDouble(int* p) {
*p = *p * *p; // 주소를 통해 값을 직접 수정
}
여기서 중요한 점은, ChangeDouble 함수가 num의 값을 직접 수정하기 때문에 함수 호출 후에도 num의 값이 제곱된 상태로 남아있다는 것입니다. 따라서 리턴 없이도 값이 바뀝니다.
정리하자면:
- 포인터를 통해 변수의 메모리 주소에 직접 접근해서 값을 변경하기 때문에, 함수가 리턴하지 않아도 num의 값이 바뀌는 것입니다.
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int num;
int* numP = #// int num의 주소를 가리키도록
printf("숫자를 입력하시오 : ");
scanf_s("%d", &num); /*&num은 num의 변수 주소*/
printf("num : %d\n*numP : %d\n\n", num, *numP); // 내용 가져옴
printf("&num : %d\nnumP : %d\n\n", &num, numP); //주소 가져옴
Q.여기에서 num의 값을 numP포인터에 넣었다는건 numP에 num의 주소값이 들어가는 거야?
A.네, 맞습니다! numP 포인터에 num의 주소값이 들어가는 것입니다. 이 부분을 좀 더 자세히 설명드릴게요.
int num;
int* numP = # // num의 주소를 numP 포인터에 저장
- int num;: num이라는 정수형 변수를 선언합니다. 이 변수는 메모리의 어떤 위치에 값을 저장하게 됩니다.
- int* numP = #:
- int* numP는 num이라는 정수형 변수의 주소를 저장할 수 있는 포인터입니다.
- &num은 num 변수의 메모리 주소를 의미합니다.
- 이 문장은 numP라는 포인터 변수에 num의 메모리 주소를 저장하는 작업입니다. 즉, numP는 num이 저장된 위치를 가리킵니다.
요약:
- *numP*는 num의 주소를 가리키고 있습니다. 즉, num이 메모리에서 어디에 저장되어 있는지 그 주소값을 numP가 저장합니다.
- *numP*는 numP가 가리키는 주소에 있는 값을 의미합니다. 즉, numP는 num의 값과 동일한 값을 가리키게 됩니다.
출력 예시:
printf("num : %d\\n*numP : %d\\n\\n", num, *numP);
- num은 num 변수의 값을 출력합니다.
- numP는 numP가 가리키는 주소에 저장된 값을 출력하는데, 이는 num과 동일합니다.
printf("&num : %d\\nnumP : %d\\n\\n", &num, numP);
- &num은 num 변수의 메모리 주소를 출력합니다.
- numP도 동일하게 num의 메모리 주소를 가리키고 있으므로, &num과 numP는 같은 값을 출력합니다.
결론:
- numP에 num의 주소값이 들어간다는 것은, numP가 이제 num이 저장된 메모리 위치를 가리키고 있다는 뜻입니다.
- numP를 통해 num의 값을 접근할 수 있습니다.
Q.int* numP라고 선언했잖아 그럼 numP야? 아님 *numP야? printf에서 사용하고 싶으면 어떻게 써야해?
A.int* numP 선언에서 numP와 *numP의 차이점과 각각을 어떻게 사용해야 하는지 설명해드릴게요.
1. int* numP 선언
- int* numP: 여기서 numP는 포인터 변수입니다. 즉, 정수형 값을 저장하는 변수가 아니라, 정수형 변수가 저장된 메모리 주소를 저장하는 변수입니다.
- numP: 포인터 변수 numP가 가리키고 있는 주소에 저장된 값을 참조할 때 사용합니다.
2. numP와 * numP의 차이
- numP: num 변수의 주소를 가리키는 포인터입니다. 즉, numP에는 num의 메모리 주소가 저장됩니다.
- * numP: numP가 가리키는 주소에 저장된 값을 나타냅니다. 즉, * numP는 num의 값을 의미합니다.
3. printf에서 사용법
- numP: 메모리 주소를 출력할 때 사용합니다.numP는 num이 저장된 주소를 가지고 있기 때문에, printf에서 주소값을 출력하려면 %p 형식을 사용해야 합니다.
- printf("numP : %p\\n", numP); // %p는 포인터 주소를 출력하는 포맷
- * numP: 포인터가 가리키는 값을 출력할 때 사용합니다.
- * numP는 numP가 가리키는 주소의 값을 의미합니다. 즉, num의 값과 동일한 값을 출력하게 됩니다.
- printf("*numP : %d\\n", *numP); // %d는 정수 값을 출력하는 포맷
코드 예시
#include <stdio.h>int main() {
int num = 10;
int* numP = # // num의 주소를 numP에 저장
printf("num : %d\\n", num); // num의 값 출력
printf("*numP : %d\\n", *numP); // numP가 가리키는 값 출력
printf("numP : %p\\n", numP); // numP, 즉 num의 주소 출력
printf("&num : %p\\n", &num); // num의 주소 출력 (numP와 동일)
return 0;
}
출력 결과:
num : 10
*numP : 10
numP : 0x7ffee6b1a658 // (주소값은 시스템에 따라 다름)
&num : 0x7ffee6b1a658 // numP와 동일한 주소 출력
요약
- *numP*는 주소를 출력할 때 사용합니다. (%p)
- *numP*는 그 주소에 저장된 값을 출력할 때 사용합니다. (%d)
- 포인터 변수와 그 값에 접근하는 방법을 정확히 구분하는 것이 중요합니다!
Q.그러면 그냥 numP로 선언하고 주소를 사용하고 싶을때 &numP로 사용하면 안돼?
A.먼저 포인터 변수와 일반 변수의 개념을 정확히 이해하는 것이 중요합니다. 각각의 역할이 다르기 때문에, 단순히 numP로 선언하고 &numP를 사용하려는 시도는 잘못된 방식입니다. 그 이유를 설명드리겠습니다.
1. 포인터와 일반 변수의 차이
- 포인터 변수는 다른 변수의 주소를 저장하는 변수입니다. 예를 들어, int* numP;는 정수형 변수를 가리킬 수 있는 포인터를 선언하는 것입니다.
- 일반 변수는 값을 직접 저장합니다. 예를 들어, int num;는 정수 값을 저장하는 변수입니다.
2. & 연산자와 포인터의 역할
- & 연산자: 어떤 변수 앞에 붙이면 그 변수의 주소를 반환합니다.
- 예: &num은 num 변수의 메모리 주소를 반환합니다.
- 포인터 변수: 다른 변수의 주소를 저장하는 역할을 합니다.
- 예: int* numP = #는 num의 주소를 numP에 저장합니다.
3. 왜 &numP를 사용하면 안 되는가?
numP를 그냥 일반 변수처럼 선언하고 &numP를 사용하려면, numP는 포인터가 아닌 일반 변수가 되어야 합니다. 그러나 이 경우, numP에는 주소를 저장할 수 없고, 주소를 참조할 수 있는 능력도 없습니다.
- numP가 포인터가 아닌 경우: numP는 주소를 가리키지 않고, 단순히 정수 같은 값을 저장하는 일반 변수가 됩니다.
- 예를 들어, int numP;로 선언한다면 numP는 정수값을 저장하는 일반 변수가 됩니다. 이때 &numP는 numP 자신의 주소를 반환할 뿐, 다른 변수의 주소와는 아무 관련이 없습니다.
4. 올바른 방법: 포인터 변수로 선언
주소를 다루고 싶다면, 포인터 변수를 사용해야 합니다. 즉, numP를 포인터로 선언해야만 num과 같은 다른 변수의 주소를 저장하고 이를 활용할 수 있습니다.
int num;
int* numP = # // num의 주소를 numP에 저장
- *numP*는 이제 num의 주소를 가리키는 포인터입니다.
- *&numP*를 사용하면, numP 자신의 주소를 반환하게 됩니다. 이는 num의 주소와는 전혀 다른 값입니다.
정리
- 포인터 변수는 다른 변수의 주소를 저장할 수 있도록 선언해야 합니다. (int* numP;)
- & 연산자는 변수의 주소를 반환하지만, 포인터가 아닌 변수에는 주소를 저장할 수 없습니다.
- *&numP*를 사용하면, numP 자체의 주소를 반환하게 되므로, 원하는 주소(num의 주소)와는 다릅니다.
따라서, 다른 변수의 주소를 저장하려면 반드시 포인터 변수로 선언하고, 그 포인터를 통해 주소를 다루는 것이 맞습니다.
Q.그럼 포인터 변수를 사용할 때는 항상 일반변수를 선언하고 그 주소를 저장해줘야해?
A.포인터 변수를 사용할 때 항상 일반 변수를 선언하고 그 주소를 저장해야 하는 것은 아닙니다, 하지만 일반적으로 그렇게 사용하는 경우가 많습니다. 포인터는 다양한 상황에서 활용될 수 있기 때문에, 일반 변수의 주소를 저장하는 것 외에도 여러 방식으로 사용할 수 있습니다.
1. 포인터의 일반적인 사용: 일반 변수의 주소 저장
포인터를 가장 많이 사용하는 경우는 일반 변수의 주소를 저장하는 상황입니다. 이때, 일반 변수를 선언하고 그 주소를 포인터에 저장하여 사용합니다.
예시:
int num = 10; // 일반 변수 선언
int* numP = # // num의 주소를 포인터에 저장
이 방법은 다른 함수에서 값을 수정하거나 메모리를 효율적으로 다루기 위해 많이 사용됩니다.
2. 포인터만 선언하고 나중에 메모리 할당
포인터는 선언만 하고 나중에 메모리를 동적으로 할당할 수 있습니다. 이 경우에는 일반 변수를 선언하지 않고 포인터가 가리킬 메모리를 직접 할당할 수 있습니다.
예시: 동적 메모리 할당
int* numP;
numP = (int*)malloc(sizeof(int)); // 동적으로 메모리 할당
*numP = 20; // 포인터가 가리키는 메모리에 값 저장
위 예시에서는 malloc 함수로 메모리를 동적으로 할당하고, 그 메모리의 주소를 numP에 저장합니다. 이렇게 하면 일반 변수를 선언할 필요 없이 포인터가 직접 메모리 공간을 가리킬 수 있습니다.
3. 배열과 포인터
포인터는 배열을 가리키는 데도 자주 사용됩니다. 배열 이름 자체가 배열의 첫 번째 요소의 주소를 나타내기 때문에 포인터와 배열은 밀접하게 연관되어 있습니다.
예시: 배열을 가리키는 포인터
int arr[5] = {1, 2, 3, 4, 5};
int* p = arr; // 배열의 첫 번째 요소의 주소를 가리킴
printf("%d\\n", *p); // 1 출력
배열 이름은 첫 번째 요소의 주소를 나타내므로, p = arr;는 p = &arr[0];와 동일한 효과를 가집니다. 배열의 주소를 저장하는 데도 일반 변수를 선언할 필요가 없습니다.
4. 함수와 포인터
함수 매개변수로 포인터를 사용할 때도 일반 변수를 포인터로 전달하여 값이나 주소를 다룰 수 있습니다. 하지만 경우에 따라 포인터만 전달하고 그 포인터가 가리킬 값을 함수 안에서 생성할 수 있습니다.
예시: 포인터를 통해 함수 내에서 값 설정
void set_value(int* p) {
*p = 100; // 포인터가 가리키는 메모리 주소에 값을 저장
}
int main() {
int num;
set_value(&num); // num의 주소를 함수에 전달
printf("%d\\n", num); // 100 출력
}
결론:
- 일반 변수를 선언하고 그 주소를 포인터에 저장하는 것은 매우 일반적인 사용 방식입니다.
- 그러나 포인터는 동적 메모리 할당, 배열 처리, 함수 인자 전달 등 다양한 방식으로 사용할 수 있으며, 일반 변수를 선언하지 않고도 사용할 수 있는 경우가 많습니다.
- 동적 메모리 할당을 통해 직접 메모리 공간을 관리하거나, 배열을 가리키는 포인터로 활용할 수 있습니다.
Q.주소값을 바꿀수도 있어?
A.포인터가 가리키는 주소값은 변경할 수 있습니다. 하지만 중요한 것은 포인터가 가리키는 대상의 주소를 바꾸는 것이지, 실제 메모리 주소 자체를 바꾸는 것은 아닙니다. 포인터는 변수처럼 다른 메모리 위치를 가리킬 수 있습니다. 즉, 포인터가 가리키는 주소값을 변경해서 다른 변수를 가리키게 할 수 있습니다.
1. 포인터가 가리키는 주소값을 바꾸는 예시
포인터는 선언된 후, 다른 변수나 메모리의 주소를 가리키도록 변경할 수 있습니다.
#include <stdio.h>int main() {
int num1 = 10;
int num2 = 20;
int* p = &num1; // 포인터 p가 num1의 주소를 가리킴
printf("p가 가리키는 값: %d\\n", *p); // 10 출력
p = &num2; // 포인터 p가 num2의 주소로 변경됨
printf("p가 가리키는 값: %d\\n", *p); // 20 출력
return 0;
}
설명:
- 처음에 포인터 p는 num1의 주소를 가리키고 있습니다. 그래서 p는 num1의 값인 10을 출력합니다.
- 그 후, p가 num2의 주소를 가리키도록 바꿨습니다. 이제 p는 num2의 값인 20을 출력합니다.
이처럼 포인터가 가리키는 주소값은 언제든지 변경할 수 있습니다. 이 경우, 메모리 주소 자체는 변하지 않고, 포인터가 가리키는 주소만 변경됩니다.
2. 동적 메모리 할당과 포인터 주소 변경
동적 메모리 할당 시에도 포인터가 가리키는 메모리 주소를 바꿀 수 있습니다.
#include <stdio.h>
#include <stdlib.h>
int main() {
int* p = (int*)malloc(sizeof(int)); // 메모리 할당
*p = 30; // p가 가리키는 메모리에 30 저장
printf("p가 가리키는 값: %d\\n", *p); // 30 출력
free(p); // 할당된 메모리 해제
// 다른 메모리 할당
p = (int*)malloc(sizeof(int));
*p = 40; // 새로운 메모리에 40 저장
printf("p가 가리키는 값: %d\\n", *p); // 40 출력
free(p); // 메모리 해제
return 0;
}
설명:
- 처음에 malloc을 사용해 메모리를 할당하고, 포인터 p는 그 메모리 주소를 가리킵니다. 이후 할당된 메모리에 30을 저장합니다.
- 메모리를 해제한 후, 새로운 메모리를 할당하고, 포인터 p는 새로운 메모리 주소를 가리키게 됩니다.
3. 포인터가 가리키는 주소를 바꿀 수 있지만, 주소 자체는 바꿀 수 없다
포인터는 다른 변수나 메모리 주소를 가리키도록 변경할 수 있습니다. 그러나 실제 메모리 주소 자체는 운영체제에 의해 할당된 것이므로 프로그램에서 변경할 수 없습니다. 프로그램에서는 포인터가 어디를 가리킬지를 제어할 수 있을 뿐, 메모리 주소 자체를 임의로 바꿀 수는 없습니다.
결론:
- 포인터가 가리키는 주소값은 자유롭게 바꿀 수 있습니다. 포인터가 다른 변수를 가리키게 하거나, 동적 메모리 할당으로 새로운 메모리 주소를 가리킬 수 있습니다.
- 하지만 메모리 주소 자체는 변경할 수 없습니다. 운영체제가 메모리 주소를 할당하며, 프로그래머는 그 주소를 활용할 수 있을 뿐입니다.
Q.포인터 변수 p가 있는데 *p에 값을 넣는거랑 p에 값을 넣는거랑 어떻게 달라?
A.포인터 변수 p와 포인터를 통해 참조하는 값 *p는 서로 다른 개념이므로, p에 값을 넣는 것과 *p에 값을 넣는 것은 전혀 다른 결과를 가져옵니다.
1. p에 값을 넣는 경우:
- p는 메모리 주소를 저장하는 변수입니다. 따라서 p에 값을 넣는다는 것은 p가 가리킬 메모리 주소를 설정하는 것입니다. 즉, p가 가리키는 위치가 바뀝니다.
예시:
int num1 = 10;
int num2 = 20;
int* p = &num1; // p가 num1의 주소를 가리킴
printf("p가 가리키는 주소: %p\\n", p); // p의 값 (num1의 주소)
p = &num2; // p가 num2의 주소를 가리키게 변경됨
printf("p가 가리키는 주소: %p\\n", p); // p의 값 (num2의 주소)
설명:
- 처음에 p는 num1의 주소를 가리키고 있었지만, p = &num2;를 통해 num2의 주소를 가리키게 변경되었습니다. p에 새로운 값을 넣으면, 포인터가 가리키는 주소가 바뀝니다.
2. p에 값을 넣는 경우:
- p는 포인터가 가리키는 메모리 주소에 저장된 값을 의미합니다. 따라서 p에 값을 넣는다는 것은 포인터가 가리키는 메모리 공간에 새로운 값을 저장하는 것입니다. 포인터가 가리키는 주소의 값이 변경됩니다.
예시:
int num = 10;
int* p = # // p는 num의 주소를 가리킴
printf("num의 값: %d\\n", num); // num의 값은 10
*p = 20; // p가 가리키는 주소에 20을 저장 (즉, num의 값이 20으로 변경됨)
printf("num의 값: %d\\n", num); // num의 값은 20
설명:
- 처음에 num은 10이었지만, p = 20;을 통해 p가 가리키는 num의 값을 20으로 변경했습니다. p는 포인터가 가리키는 값에 접근하는 것이므로, 해당 값이 바뀝니다.
3. 요약:
- p = 주소값: 포인터 p가 가리키는 주소를 변경합니다. 즉, 포인터가 다른 변수를 가리키게 합니다.
- p = 값: 포인터 p가 가리키는 주소에 저장된 값을 변경합니다. 즉, 포인터가 가리키는 메모리 공간에 새로운 값을 저장합니다.
전체 예시 코드:
#include <stdio.h>int main() {
int num1 = 10;
int num2 = 20;
int* p = &num1; // p는 num1의 주소를 가리킴
printf("num1: %d, num2: %d\\n", num1, num2); // num1: 10, num2: 20
printf("p가 가리키는 값: %d\\n", *p); // p가 가리키는 값: 10
*p = 30; // p가 가리키는 num1의 값을 30으로 변경
printf("num1: %d, num2: %d\\n", num1, num2); // num1: 30, num2: 20
p = &num2; // p가 이제 num2의 주소를 가리키도록 변경
printf("p가 가리키는 값: %d\\n", *p); // p가 가리키는 값: 20
*p = 40; // p가 가리키는 num2의 값을 40으로 변경
printf("num1: %d, num2: %d\\n", num1, num2); // num1: 30, num2: 40
return 0;
}
결과:
num1: 10, num2: 20
p가 가리키는 값: 10
num1: 30, num2: 20
p가 가리키는 값: 20
num1: 30, num2: 40
결론:
- *p = 주소값*은 포인터가 가리키는 주소를 바꾸는 것이고,
- *p = 값*은 포인터가 가리키는 주소에 저장된 값을 바꾸는 것입니다.
이해가 잘 되지 않는다면 c언어에만 있는 메모리 구조에 대해 이해해야 하는데
엑셀에서 주소를 지정하는 개념이라고 생각하면 쉽다.
위의 상황에서 *p=20을 넣으면 단순히 num 이 지워지고 20이 되는게 아닌 점에서
내가 이해하지 못한 점이 었는데 이건 엑셀을 생각하면 조금 이해가 된다
A1이 num인거고 B2가 p인건데 *p에 값을 넣는 다는 것 자체가 A1에 값을 넣는 것과 같으므로
num역시 20으로 바뀐거라고 이해했다.
c언어가 정보처리기사 실기에서 자바와 함께 3-4문제가 출제된다고 한다. 파이썬은 1-2문항
물론 포인터 *이니 &를 보는 순간부터 어질,,,ㅎ해 하기보단 차근차근 이해해나가면 해볼만 하다.