포인터란?
- 포인터: 주소를 가지고 있는 변수
변수는 어디에 저장되는가?
- 변수는 메모리에 저장된다.
- 메모리는 바이트 단위로 액세스된다.
변수와 메모리
- 변수의 크기에 따라서 차지하는 메모리 공간이 달라진다.
변수의 주소
- 변수의 주소를 계산하는 연산자: &
- 변수 i의 주소: &i
//변수의 주소 출력하기
#include <stdio.h>
int main(void) {
int i = 10;
char c = 69;
float f = 12.3;
printf("i의 주소: %p\n", &i);
printf("c의 주소: %p\n", &c);
printf("f의 주소: %p\n", &f);
return 0;
}
포인터의 선언
- 포인터: 변수의 주소를 가지고 있는 변수
포인터와 변수의 연결
int i = 10; //정수형 변수 i 선언
int* p; //포인터 변수 p 선언
p = &i; //변수 i의 주소가 포인터 p로 대입됨
다양한 포인터의 선언
char c = 'A'; //문자형 변수 c
float f = 36.5; //실수형 변수 f
double d = 3.14; //실수형 변수 d
char* pc = &c; //문자를 가리키는 포인터 pc
float* pf = &f; //실수를 가리키는 포인터 pf
double* pd = &d; //실수를 가리키는 포인터 pd
//포인터 선언 예제
#include <stdio.h>
int main(void) {
int i = 10;
double f = 12.3;
int* pi = NULL; //NULL: 0번지
double* pf = NULL;
pi = &i;
pf = &f;
printf("%p %p\n", pi, &i); //포인터 == i의 주소
printf("%p %p\n", pf, &f); //포인터 == f의 주소
return 0;
}
간접 참조 연산자
- 간접 참조 연산자 *: 포인터가 가리키는 값을 가져오는 연산자
int i = 10;
int* p;
p = &i;
printf("%d\n", *p);
& 연산자와 * 연산자
- & 연산자: 변수의 주소 반환
- * 연산자: 포인터가 가리키는 곳의 내용 반환
//포인터 예제1
#include <stdio.h>
int main(void) {
int i = 3000;
int* p = NULL;
p = &i; //포인터 p에 정수형 변수 i의 주소 대입
printf("p=%p\n", p);
printf("&i=%p\n", &i); //i형 변수의 주소
printf("i=%d\n", i);
printf("*p=%d\n", *p); //포인터 p가 가리키는 곳의 내용
return 0;
}
//포인터 예제2
#include <stdio.h>
int main(void) {
int x = 10, y = 20;
int* p;
p = &x;
printf("p=%p\n", p);
printf("*p=%u\n", *p); //%u
p = &y;
printf("p=%p\n", p);
printf("*p=%u\n", *p); //%u
return 0;
}
//포인터 예제3
//포인터를 이용하여 변수의 값 변경하기
#include <stdio.h>
int main(void) {
int i = 10;
int* p;
p = &i; //포인터 p = 변수 i의 주소
printf("i=%d\n", i);
*p = 20; //포인터를 이용하여 변수의 값 변경
printf("i=%d\n", i);
return 0;
}
Lab: 임베디드 프로그래밍 체험1
//임베디드 프로그래밍 체험1
#include <stdio.h>
int main(void) {
volatile char* p = (volatile char*)0x30000000;
int i;
while (1) {
*p |= 0x1; //첫 번째 비트를 1로 만든다. 다른 비트들은 건드리지 않는다.
for (i = 0; i < 100000; i++) //시간 지연 루프
*p &= ~(0x1); //첫 번째 비트를 0으로 만든다.
for (i = 0; i < 100000; i++); //시간 지연 루프
}
return 0;
}
포인터 사용 시 주의점
1) 초기화가 안 된 포인터를 사용하면 안 된다.
포인터가 아무것도 가리키고 있지 않은 경우에는 NULL로 초기화
- int *p=NULL;
#include <stdio.h>
int main(void) {
int* p; //포인터 p는 초기화가 안 되어 있음
*p = 100;
return 0;
}
2) 포인터의 타입과 변수의 타입은 일치하여야 한다.
#include <stdio.h>
int main(void) {
int i;
double* pd;
pd = &i; //오류!!!
*pd = 36.5; //포인터를 이용하여 변수의 값 변경
return 0;
}
포인터 연산
▶ 가능한 연산: 증가, 감소, 덧셈, 뺄셈 연산
▶ 증가 연산의 경우, 증가되는 값은 포인터가 가리키는 객체의 크기이다.
- char: 1
- short: 2
- int: 4
- float: 4
- double: 8
//증가 연산 예제
#include <stdio.h>
int main(void) {
char* pc;
int* pi;
double* pd;
pc = (char*)10000; //char형 변수: 1
pi = (int*)10000; //int형 변수: 4
pd = (double*)10000; //double형 변수: 8
printf("pc=%u, pc+1=%u, pc+2=%u\n", pc, pc + 1, pc + 2); //1
printf("pi=%u, pi+1=%u, p1+2=%u\n", pi, pi + 1, pi + 2); //4
printf("pd=%u, pd+1=%u, pd+2=%u\n", pd, pd + 1, pd + 2); //8
return 0;
}
간접 참조 연산자와 증감 연산자
- *p++; --> p가 가리키는 위치에서 값을 가져온 후에 p를 증가한다.
- (*p)++; --> p가 가리키는 위치의 값을 증가한다.
//포인터의 증감 연산
#include <stdio.h>
int main(void) {
int i = 10;
int* pi = &i;
printf("i=%d, pi=%p\n", i, pi);
(*pi)++; //pi가 가리키는 위치의 값을 증가한다.
printf("i=%d, pi=%p\n", i, pi);
*pi++; //pi가 가리키는 위치에서 값을 가져온 후에 pi를 증가한다.
printf("i=%d, pi=%p\n", i, pi);
return 0;
}
포인터의 형변환
double* pd = &f;
int* pi;
pi = (int*)pd; //pi에 pd의 값을 대입(int형으로 변환하여)
//포인터 형변환 예제
#include <stdio.h>
int main(void) {
int data = 0x0A0B0C0D;
char* pc;
int i;
pc = (char*)&data;
for (i = 0; i < 4; i++)
printf("*(pc+%d)=%02X\n", i, *(pc + i)); //*(pc+i)
return 0;
}
참고!
- 포인터는 우리가 마음대로 증감시킬 수 있지만, 증감된 포인터가 잘못된 위치를 가리킬 수도 있다.
- 포인터는 우리가 만든 데이터가 아닌 남의 데이터를 가리킬 수도 있고, 운영체제가 사용하는 데이터 영역을 가리킬 수도 있다.
함수 호출 시에 인수 전달 방법
▶ 값에 의한 호출(call by value)
- 함수로 복사본이 전달된다.
- C언어에서의 기본적인 방법
▶ 참조에 의한 호출(call by reference)
- 함수로 원본이 전달된다.
- C에서는 포인터를 이용하여 흉내낼 수 있다.
값에 의한 호출
▶ swap() 함수
//swap 함수 예제
#include <stdio.h>
void swap(int x, int y); //함수 선언
int main(void) {
int a = 100, b = 200;
printf("a=%d, b=%d\n", a, b);
swap(a, b); //swap 함수
printf("a=%d, b=%d\n", a, b);
return 0;
}
void swap(int x, int y) {
int temp;
printf("x=%d, y=%d\n", x, y);
temp = x;
x = y;
y = temp;
printf("x=%d, y=%d\n", x, y);
}
참조에 의한 호출
▶ swap() 함수
#include <stdio.h>
void swap(int *px, int *py);
int main(void) {
int a = 100, b = 200;
printf("a=%d, b=%d\n", a, b);
swap(&a, &b); //px = &a, &를 붙인다.
printf("a=%d, b=%d\n", a, b);
return 0;
}
void swap(int* px, int* py) {
int temp;
temp = *px;
*px = *py;
*py = temp;
}
scanf() 함수
- scanf(): 변수에 값을 저장하기 위하여 변수의 주소를 받는다.
※ 참고: 함수가 포인터를 통하여 값을 변경할 수 없게 하려면?
--> 함수의 매개 변수를 선언할 때 앞에 const를 붙이면 된다. const를 앞에 붙이면 포인터가 가리키는 내용이 변경 불가능한 상수라는 뜻이 된다.
void sub(const int* p) {
*p = 0; //오류!!!
}
예제: 기울기와 y절편 계산
- 만약 함수가 하나 이상의 값을 반환하여야 한다면, 포인터를 사용하는 것이 하나의 방법이다.
//포인터 예제: 기울기와 y절편을 계산
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int get_line_parameter(int x1, int y1, int x2, int y2, float* slope, float* yintercept) {
if (x1 == x2)
return -1;
else {
*slope = (float)(y2 - y1) / (float)(x2 - x1); //기울기
*yintercept = y1 - (*slope) * x1; //y절편
return 0;
}
}
int main(void) {
float s, y;
if (get_line_parameter(3, 3, 6, 6, &s, &y) == -1)
printf("에러\n");
else
printf("기울기는 %f, y절편은 %f\n", s, y);
return 0;
}
포인터를 반환할 때 주의점
- 함수가 종료되더라도 남아있는 변수의 주소를 반환하여야 한다.
- 지역 변수의 주소를 반환하면, 함수가 종료되면 사라지기 때문에 오류이다.
포인터와 배열
- 배열과 포인터는 아주 밀접한 관계를 가지고 있다. 배열 이름이 바로 포인터이다.
- 포인터는 배열처럼 사용이 가능하다.
//포인터와 배열의 관계1
#include <stdio.h>
int main(void) {
int a[] = { 10, 20, 30, 40, 50 };
printf("&a[0]=%u\n", &a[0]);
printf("&a[1]=%u\n", &a[1]);
printf("&a[2]=%u\n", &a[2]);
printf("a=%u\n", a);
return 0;
}
- 인덱스 표기법을 포인터에 사용할 수 있다.
//포인터를 배열처럼 사용
#include <stdio.h>
int main(void) {
int a[] = { 10, 20, 30, 40, 50 };
int* p;
p = a;
printf("a[0]=%d, a[1]=%d, a[2]=%d\n", a[0], a[1], a[2]); //10 20 30
printf("p[0]=%d, p1[1]=%d, p[2]=%d\n", p[0], p[1], p[2]);
printf("\n");
//배열은 결국 포인터로 구현된다.
p[0] = 60, p[1] = 70, p[2] = 80;
printf("a[0]=%d, a[1]=%d, a[2]=%d\n", a[0], a[1], a[2]); //60 70 80
printf("p[0]=%d, p1[1]=%d, p[2]=%d\n", p[0], p[1], p[2]);
return 0;
}
배열 매개 변수
- 일반 매개 변수 vs. 배열 매개 변수
- 배열 매개 변수는 포인터로 생각할 수 있다.
//일반 매개변수
void sub(int x) { //매개변수 x에 기억장소가 할당된다.
...
}
//배열 매개변수
void sub(int b[]) { //b에 기억장소가 할당되지 않는다.
...
}
//포인터와 함수의 관계
#include <stdio.h>
void sub(int b[], int n); //sub 함수 선언
int main(void) {
int a[3] = { 1, 2, 3 };
printf("%d %d %d\n", a[0], a[1], a[2]); //1 2 3
sub(a, 3); //sub 함수
printf("%d %d %d\n", a[0], a[1], a[2]); //4 5 6
return 0;
}
void sub(int b[], int n) {
b[0] = 4;
b[1] = 5;
b[2] = 6;
}
다음 2가지 방법은 완전히 동일하다.
- 배열 표기법
- 포인터 표기법
//포인터 매개 변수
void sub(int* b, int size) {
//int *b -- 배열 이름과 포인터는 근본적으로 같다.
b[0] = 4; //배열 표기법
b[1] = 5;
b[2] = 6;
}
//포인터 매개 변수
void sub(int* b, int size) {
//int *b
*b = 4; //포인터 표기법
*(b + 1) = 5;
*(b + 2) = 6;
}
포인터를 사용한 방법의 장점
- 포인터가 인덱스 표기법보다 빠르다.
<-- 이유: 인덱스를 주소로 변환할 필요가 없다.
//인덱스 표기법
int get_sum1(int a[], int n) {
int i;
int sum = 0;
for (i = 0; i < n; i++)
sum += a[i];
return sum;
}
//포인터
int get_sum2(int a[], int n) {
int i;
int sum = 0;
int* p; //포인터
p = a; //포인터 = 배열
for (i = 0; i < n; i++)
sum += *p++;
return sum;
}
Lab: 영상 처리
: 이미지 내의 모든 픽셀의 값을 10씩 증가시킨다.
#include <stdio.h>
#define SIZE 5
void print_image(int image[][SIZE]) { //print_image 함수
int r, c;
for (r = 0; r < SIZE; r++) {
for (c = 0; c < SIZE; c++) {
printf("%03d ", image[r][c]);
}
printf("\n");
}
printf("\n");
}
void briten_image(int image[][SIZE]) { //briten_image 함수
int r, c;
int* p;
p = &image[0][0]; //초기화
for (r = 0; r < SIZE; r++) {
for (c = 0; c < SIZE; c++) {
*p += 10;
p++;
}
}
}
int main(void) {
int image[5][5] = {
{10, 20, 30, 40, 50},
{10, 20, 30, 40, 50},
{10, 20, 30, 40, 50},
{10, 20, 30, 40, 50},
{10, 20, 30, 40, 50} };
print_image(image);
briten_image(image);
print_image(image);
return 0;
}
포인터 사용의 장점
- 연결 리스트, 이진 트리 등의 향상된 자료구조를 만들 수 있다.
- 참조에 의한 호출: 포인터를 매개변수로 이용하여 함수 외부의 변수 값을 변경할 수 있다.
- 매모리 매핑 하드웨어: 메모리 매핑 하드웨어란, 메모리처럼 접근할 수 있는 하드웨어 장치를 의미한다.
volatile int* hw_address = (volatile int*)0x7FFF;
*hw_address = 0x0001; //주소 0x7FFF에 있는 장치에 0x0001을 쓴다.
- 동적 메모리 할당
'Programming > 컴퓨터프로그래밍및실습' 카테고리의 다른 글
[컴프실] 제13장: 구조체 (0) | 2023.08.29 |
---|---|
[컴프실] 제12장: 문자와 문자열 (0) | 2023.08.27 |
[컴프실] 제10장-2: 정렬 (0) | 2023.07.23 |
[컴프실] 9~12일차 실습문제 (0) | 2023.07.20 |
[컴프실] 제10장: 배열 (0) | 2023.07.19 |