사자자리

[C언어] 5주차 포인터 본문

C언어/C언어 이론

[C언어] 5주차 포인터

renne 2022. 5. 20. 19:11

컴퓨터 메모리

 - 메모리 셀(cell)이 연속적으로 나열된 형태

 - 하나의 메모리 셀1 byte 데이터가 저장 (1 byte = 8 bits)

 - 각 메모리 셀에는 주소가 부여된다.

 - 데이터는 각 데이터 크기에 필요한 만큼 메모리 셀을 차지한다. ex) short: 2 byte, int: 4 byte, double: 8 byte

 - 한 데이터가 차지하는 메모리 셀의 개수는 sizeof( )로 알 수 있다.

변수와 메모리 주소

#include <stdio.h>
int main(){
    int num = 10;
    printf("변수 num의 주소: %p", &num);
    return 0;
}

<실행 결과>
변수 num의 주소: 000000000062FE1C

 - 생성된 변수는 메모리에 저장된다.

 -  & : 주소 연산자. 변수명 앞에서만 사용할 수 있다.

 -  %p : 메모리 주소의 서식 지정자. pointer의 p를 사용한다.

 - 메모리 주소는 컴퓨터마다, 실행할 때마다 달라진다.

 

포인터 변수(포인터)

 - 특정 변수의 메모리 주소를 저장하는 변수

 - 주소를 이용해서 특정 변수에 접근할 수 있게 한다.

 - 포인터 변수의 데이터형과 포인터 변수가 가리키는 변수의 데이터형은 일치해야 한다.

 - 포인터 변수를 선언할 때, 자료형 뒤에  * (asterisk)를 붙인다. 위치는 상관없다.

int* pointer;
int *pointer;
int * pointer;

 

 -  * : 간접 참조 연산자. 포인터 변수가 가리키는 변수에 접근해서 값을 읽거나 변경한다.

#include <stdio.h>
int main(){
    int *pointer;	//포인터 변수 선언
    int num = 10;
    pointer = &num;	//변수 num의 주소를 포인터 변수에 저장
    
    printf("변수 num의 주소: %p\n", pointer);
    printf("변수 num의 값: %d\n", *pointer);	//간접참조연산자 사용
    return 0;
}

<실행 결과>
변수 num의 주소: 000000000062FE14
변수 num의 값: 10

 

 - 포인터 변수의 크기는 가리키는 변수의 데이터형에 상관없이 항상 같다.

#include <stdio.h>
int main(){
    char *a;	//char 변수의 크기: 1 바이트
    int *b;	//int 변수의 크기: 4 바이트
    double *c;	//double 변수의 크기: 8 바이트

    printf("%d\n", sizeof(a));
    printf("%d\n", sizeof(b));
    printf("%d\n", sizeof(c));
    return 0;
}

<실행 결과>
8
8
8

 

포인터 변수의 초기화

 - 포인터 변수를 초기화하지 않고 사용하면 실행 에러가 발생한다.

int *p;		//쓰레기값을 가진다.
*p = 10;	//실행 에러

 

 - NULL 포인터: 포인터가 다른 변수를 가리키지 않을 때는 null(0)로 초기화한다.

#include <stdio.h>
int main(){
    int *p = NULL;
    printf("%p", p);
    return 0;
}

<실행 결과>
0000000000000000

 

포인터의 연산

 - 포인터에 연산을 취하면, 포인터가 가리키는 자료의 크기 단위로 증감한다.

 - 포인터 변수 + n: 포인터가 가리키는 데이터형 n개 크기만큼 증가된 주소가 연산의 결과

 - 포인터 변수 – n: 포인터가 가리키는 데이터형 n개 크기만큼 감소된 주소가 연산의 결과

 - 포인터 변수끼리도 연산할 수 있다.

 

 

 - 포인터의 증감 연산

#include <stdio.h>
int main(){
    int arr[5] = {1, 2, 3, 4, 5};
    int *p = &arr[0];
    for (int i = 0; i < 5; i++, p++){
    	printf("%d ", *p);
    }
    return 0;
}

<실행 결과>
1 2 3 4 5

 

포인터와 배열

 - 인덱스 없는 배열명 = 배열의 시작 주소

 - 배열명은 포인터처럼 사용할 수 있다.

 - arr = &arr[0]

#include <stdio.h>
int main(){
    int arr[3] = {1, 2, 3};
    printf("배열의 시작 주소: %p\n", arr);
    printf("배열의 시작 주소: %p\n", &arr[0]);
    return 0;
}

<실행 결과>
배열의 시작 주소: 000000000062FE10
배열의 시작 주소: 000000000062FE10

 

 - *(arr + i) = arr[i]

#include <stdio.h>
int main(){
    int arr[3] = {1, 2, 3};
    printf("%d\n", *(arr + 2));
    printf("%d\n", arr[2]);
    return 0;
}

<실행 결과>
3
3

 

 - 배열의 시작 주소로 초기화된 포인터 변수를 이용해서, 배열의 모든 원소에 접근할 수 있다.

 - p + i = &p[i]

- *(p + i) = p[i]

 

#include <stdio.h>
int main(){
    int arr[5] = {1, 2, 3, 4, 5};
    int *p = arr;

    printf("배열명을 사용하는 경우\n");
    for (int i = 0; i < 5; i++) printf("%d번째 원소: %d, 메모리 주소: %p\n", i, arr[i], arr + i);
	printf("\n");
    printf("포인터를 사용하는 경우\n");
    for (int i = 0; i < 5; i++) printf("%d번째 원소: %d, 메모리 주소: %p\n", i, *(p + i), p + i);
    return 0;
}

<실행 결과>
배열명을 사용하는 경우
0번째 원소: 1, 메모리 주소: 000000000062FDF0
1번째 원소: 2, 메모리 주소: 000000000062FDF4
2번째 원소: 3, 메모리 주소: 000000000062FDF8
3번째 원소: 4, 메모리 주소: 000000000062FDFC
4번째 원소: 5, 메모리 주소: 000000000062FE00

포인터를 사용하는 경우
0번째 원소: 1, 메모리 주소: 000000000062FDF0
1번째 원소: 2, 메모리 주소: 000000000062FDF4
2번째 원소: 3, 메모리 주소: 000000000062FDF8
3번째 원소: 4, 메모리 주소: 000000000062FDFC
4번째 원소: 5, 메모리 주소: 000000000062FE00

 

포인터와 문자열 상수

 - 문자열 리터럴: 큰따옴표("")로 둘러싼 문자의 연속체

 - 문자열 리터럴은 메모리에 보관해두고 사용하기 때문에, 문자열 리터럴은 문자열 리터럴의 주소를 의미한다.

#include <stdio.h>
int main(){
    char *p = "regulus";	//p에 "regulus"의 주소 저장
    printf("문자열 리터럴: %s\n", p);
    printf("문자열 리터럴의 주소: %p\n", p);
    return 0;
}

<실행 결과>
문자열 리터럴: regulus
문자열 리터럴의 주소: 0000000000404000

 

 - 문자열 리터럴은 메모리에 보관하지만, 변수처럼 값을 변경할 수 없다.

#include <stdio.h>
#include <string.h>
int main(){
    char *p = "regulus";
    strcpy(p, "black");	//실행 에러
    printf("%s", p);
    return 0;
}

 - char*형 변수에 다른 문자열 리터럴의 주소를 대입할 수는 있다.

 

문자열 포인터

 - char*형 변수에는 문자열의 주소문자열 리터럴의 주소를 저장할 수 있다.

#include <stdio.h>
#include <string.h>
int main(){
    //문자열을 가리키는 경우
    char str[20] = "Sirius Black";
    char *p_str = str;
    printf("%s\n", p_str);
    //포인터로 문자열의 내용을 변경할 수 있다.
    p_str[0] = 'G';
    printf("%s\n", p_str);
    strcpy(p_str, "Sirius White");
    printf("%s\n", p_str);

    //문자열 리터럴을 가리키는 경우
    char *p_literal = "Regulus Black";
    printf("%s\n", p_literal);
    //포인터로 문자열 리터럴의 내용을 변경할 수 없다.
}

<실행 결과>
Sirius Black
Girius Black
Sirius White
Regulus Black

 

 - 변경할 수 없는 문자열을 가리킬 때 const char*형을 사용한다.

const char *p = "abcde"

 

const 포인터

  포인터가 가리키는 변수의 값 포인터 변수 자신의 값 (주소)
const 데이터형 * 포인터명 읽기 O 변경 X 변경 O
데이터형 * const 포인터명 읽기 O 변경 O 변경 X
const 데이터형 * const 포인터명 읽기 O 변경 X 변경 X

 

void 포인터

 - 범용 포인터: 자료형이 정해지지 않은 포인터

 - 어떤 자료형으로 된 포인터든 모두 저장할 수 있다. 암시적으로 자료형이 변환된다.

#include <stdio.h>
int main(){
    int n = 10;
    char c = 'A';

    int *p_int = &n;
    char *p_char = &c;
    void *p_void;

    //포인터 자료형이 달라도 컴파일 경고가 발생하지 않음
    p_void = p_int;
    p_void = p_char;
    
    p_int = p_void;
    p_char = p_void;
    
    return 0;
}

 

 - 단, 간접 참조를 하려면 명시적으로 형변환을 해야한다.

#include <stdio.h>
int main(){
    int n = 1;
    char c = 'A';
    void *p;

    p = &n;
    printf("%d\n", *(int *)p);	//void 포인터를 int 포인터로 형변환하고 간접 참조

    p = &c;
    printf("%c\n", *(char *)p);	//void 포인터를 char 포인터로 형변환하고 간접 참조

    return 0;
}

<실행 결과>
1
A

 

 

함수 포인터

 - 함수를 저장하는 포인터: 함수를 호출할 수 있다.

#include <stdio.h>
float average(int a, int b){
	return (float)(a+b)/2;
}

int main(){
	printf("%p", average);	//함수 이름도 포인터이므로 메모리 주소가 있다.
    return 0;
}

<실행 결과>
0000000000401530

 

함수 포인터의 선언

 - 함수의 반환값의 자료형, 매개변수의 자료형과 개수가 일치해야 한다. 매개변수가 없으면 ()만 붙인다.

반환값의 자료형	(*포인터명)	(매개변수의 자료형);
float		(*avg)		(int, int);

 

 - 함수를 호출할 때 간접 참조하지 않아도 된다.

#include <stdio.h>
float average(int a, int b){
    return (float)(a+b)/2;
}

int main(){
    int a = 1, b = 2;
    float (*avg)(int, int);	//함수 포인터 선언
    avg = average;		//함수 포인터에 average 함수의 메모리 주소 저장
    printf(avg(a, b));
    return 0;
}

<실행 결과>
1.500000

 


 

[코딩도장] 34.9 연습문제: 포인터와 주소 연산자 사용하기
다음 소스 코드를 완성하여 10과 20이 각 줄에 출력되게 만드세요.

#include <stdio.h>
int main()
{
    int *numPtr;
    int num1 = 10;
    int num2 = 20;

    ① ________________
    printf("%d\n", *numPtr);

    ②_________________
    printf("%d\n", *numPtr);

    return 0;
}

 

#include <stdio.h>
int main()
{
    int *numPtr;
    int num1 = 10;
    int num2 = 20;

    numPtr = &num1;
    printf("%d\n", *numPtr);

    numPtr = &num2;
    printf("%d\n", *numPtr);

    return 0;
}

'C언어 > C언어 이론' 카테고리의 다른 글

[C언어] 6주차 함수의 활용  (0) 2022.06.25
[C언어] 6주차 구조체  (0) 2022.05.27
[C언어] 5주차 배열과 문자열  (0) 2022.05.20
[C언어] 4주차 함수  (0) 2022.05.14
[C언어] 4주차 제어문  (0) 2022.05.14
Comments