포인터 사용하기

  • 124 minutes to read

포인터(Pointer)는 변수가 저장된 메모리 주소를 나타냅니다. C 언어에서 포인터는 변수에 저장된 값이 어느 메모리 위치에 있는지를 알려주는 주소 값입니다. 포인터 변수를 사용하면, 값을 저장하는 것이 아니라 값이 저장된 메모리 위치를 가리키는 역할을 할 수 있습니다.

// 포인터(Pointer)는 데이터(Data)가 저장되어 있는 메모리(Memory)의 주소(Address)
IMPORTANT

포인터는 메모리 주소를 포함하는 변수입니다. 메모리 주소를 가리키는 변수입니다.

포인터는 메모리 주소를 저장하는 변수입니다. 메모리 주소로부터 값을 얻기 위해서는 포인터를 사용해야 합니다. 변수의 메모리 주소를 확인하려면 변수 이름 앞에 & 기호를 붙여야 합니다.

C 언어 포인터는 메모리 주소를 가리키는 것입니다. 이를 설명하는 가장 간단한 예제를 만들겠습니다.

#include <stdio.h>

int main(void) 
{
   int x = 7;
   int *ptr;

   ptr = &x;
   printf("x의 값은 %d\n", x);
   printf("x의 주소는 %p\n", &x);
   printf("ptr의 값은 %p\n", ptr);
   printf("ptr이 가리키는 주소에 저장된 값은 %d\n", *ptr);

   return 0;
}
x의 값은 7
x의 주소는 0x7ffeea9f6a3c
ptr의 값은 0x7ffeea9f6a3c
ptr이 가리키는 주소에 저장된 값은 7

위 코드에서, x라는 변수를 선언하고 ptr이라는 포인터 변수를 선언합니다. ptr이라는 포인터 변수는 &x의 값을 가리키게 합니다. 이후, ptr이 가리키는 메모리 주소에 접근하여 그 값을 출력하는 것을 볼 수 있습니다.

그림: 메모리에 선언된 변수

변수의 메모리 주솟값 가져오기

단항 연산자인 & 연산자를 사용하면 변수의 주소를 가져옵니다. & 연산자는 주소 연산자 또는 address-of 연산자로 부릅니다. 변수는 컴퓨터 메모리 상에 데이터를 보관해 놓는 그릇입니다. 이러한 변수는 실제로 메모리에 할당될 때 주솟값을 가집니다. 이 주솟값을 알려면 변수 이름 앞에 & 기호를 붙이면 됩니다. 이렇게 알아낸 주솟값은 %p, %x, %X 등의 서식 지정자를 사용하여 출력할 수 있습니다. 변수의 주솟값은 실행할 때마다 다를 수 있습니다.

Address-of 연산자로 변수의 주솟값 가져오기

우리는 scanf() 함수 사용할 때 이미 변수 이름 앞에 & 기호를 붙여서 사용한 경험이 있습니다.

변수 이름 앞에 & 기호를 붙이면 변수의 메모리 주솟값을 알려줍니다.

코드: variable_address.c, VariableAddress.c

// variable_address.c, VariableAddress.c 
#include <stdio.h>

int main(void)
{
    int age = 21; // int 타입 변수 선언과 동시에 21로 초기화

    printf("%d -> %p\n", age, &age); // age 변수의 값과 age 변수의 주소 출력

    return 0;
}
21 -> 000000412DBAFA84

age 변수에 & 기호를 붙여 &age 형태로 변수의 주솟값을 얻을 수 있습니다. 이 때 기본 주솟값 출력 서식 지정자는 포인터(pointer)를 나타내는 %p를 사용하면 됩니다. %d로 주솟값을 출력해도 잘 되긴합니다. 그리고 주소값의 실행 결과는 매번 다릅니다.

NOTE

변수의 주소
변수 만들 때 변수의 메모리 주소는 C 언어 컴파일러에 의해서 자동으로 결정됩니다. 사용자는 메모리 주소 할당에 신경 쓸 필요가 전혀 없습니다.

서식 지정자를 사용하여 변수의 주소 출력

코드: variable_address_format.c, 포인터_변수의주소출력.c

/*
    & 연산자와 %#x => 변수의 주소(번지) 출력
*/
#include <stdio.h>

int main(void)
{
    int i = 10;
    int j = 20;
    int k = 30;

    printf("%d, %x, %#x\n", i, i, &i); // %d => 10진수
    printf("%d, %x, %#x\n", j, j, &j); // %x => 16진수
    printf("%d, %x, %#x\n", k, k, &k); // %#x => 변수의 주소(번지;Address) 출력

    return 0;
}

결과

출력 결과는 컴파일러마다 다를 수 있습니다.

10, a, 0xe1fa60
20, 14, 0xe1fa54
30, 1e, 0xe1fa48

변수의 주소를 표현하는 서식 지정자는 %d, %x, %p, %#x 등 어떤 것을 사용해도 상관없습니다. 왜냐하면 컴파일러가 알아서 만들어주는 주소이기에 우리는 단지 상대적인 위치값만 확인하면 되기 때문입니다.

연습 문제: & 기호로 변수의 주솟값을 화면에 출력하기

코드: address_of_operator.c

#include <stdio.h>

int main(void)
{
    int year = 2023;

    printf("%d\n", year); // 2023

    // 주소는 계속 변경
    printf("%p\n", &year); // 005CFDC0, ...
    //printf("%d\n", &year); 
    //printf("%#x\n", &year);

    return 0;
}
2023
00D5FAD8

포인터 변수 선언하기

지금까지 우리가 만들고 사용한 변수들은 해당 변수의 위치에 값이 저장되는 형태입니다. 포인터 변수는 이러한 일반 변수에 대한 참조(Reference)를 하는 변수입니다. 포인터 변수에는 일반 변수 또는 데이터에 대한 위치(주소, 번지) 정보를 저장합니다.

포인터 변수를 사용할 때에는 다음 기호가 사용됩니다.

  • &: 변수의 주솟값
  • *: 포인터 변수가 가리키는 곳의 실제 데이터

포인터 변수를 선언하고 일반 변수를 참조하는 모양은 다음 두 단계를 거칩니다.

  • 데이터형식 *포인터이름;
  • 포인터변수 = &일반변수;

포인터 변수는 데이터 타입과 별표 기호(애스터리스크, *)를 함께 사용합니다. * 기호는 데이터 타입 또는 변수에 붙여서 사용해도 상관 없습니다. 다음 3개의 코드 조각은 모두 같은 의미입니다.

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

C 언어에서 일반 변수를 참조하는 포인터 변수를 그림으로 표현하면 다음과 같습니다.

그림: 포인터 변수와 일반 변수

포인터 변수와 일반 변수

일반 변수와 포인터 변수

다음 코드는 일반 변수 선언 후 포인터 변수로 참조해서 사용하는 내용입니다.

코드: pointer_variable.c, PointerVariable.c

#include <stdio.h>

int main(void)
{
    //[1] 일반 변수 선언과 동시에 초기화
    int num = 1234;

    //[2] 포인터 변수 선언과 동시에 num 변수를 ptr 변수로 참조(Reference)
    int* ptr = &num; // & 기호로 num 변수의 주솟값을 읽어 포인터 변수에 저장

    //[3] ptr 변수로 참조된 값 출력: 역참조(Dereference)
    printf("%d\n", *ptr); // * 기호로 ptr 변수가 참조하고 있는 곳의 값을 출력

    return 0;
}

결과

1234

일반 변수인 num에는 1234가 저장되어 있습니다. num 변수에 대한 참조를 &num 형태로 포인터 변수인 ptr로 참조한 후 ptr 변수의 값을 출력할 때에는 *ptr 형태를 사용합니다. & 연산자로 주소를 가져왔다면 오브젝트 연산자(Object-of operator)인 * 연산자를 사용하여 가리키는 곳의 값을 가져올 수 있습니다. * 연산자로 데이터를 가져오는 작업을 다른 말로 역참조(dereference)라고 합니다.

포인터 변수 선언 및 사용의 5가지 모양

포인터는 메모리의 특정 주소를 가리키는 변수입니다. 아래 코드는 포인터 변수를 어떻게 선언하고 사용하는지 보여주는 예제입니다.

// pointer_variable_five.c
/*
    값(Value)형 변수/배열
    포인터(참조;Reference)형 변수/배열
*/
#include <stdio.h>

int main(void)
{
    // Value Type 변수
    int a = 10;

    // Reference Type 변수: 포인터 변수
    int* pa;

    // 참조 추가
    pa = &a; // 포인터 변수에 주소 대입

    *pa = 20; // 역참조를 사용하여 포인터 변수가 참조하는 변수에 값 대입

    // 값형 변수 표현
    printf("[1] %d\n", a); // 20
    printf("[2] %p\n", &a); // 메모리 주소: 000000901175F974 번지: 계속 바뀌는 부분

    // 포인터형 변수 표현
    printf("[3] %p\n", pa); // 메모리 주소: 000000901175F974 번지
    printf("[4] %p\n", &pa); // 포인터 변수의 메모리 주소: 000000901175F998 번지
    printf("[5] %d\n", *pa); // 20

    return 0;
}
[1] 20
[2] 000000901175F974
[3] 000000901175F974
[4] 000000901175F998
[5] 20

출력 결과에서 '[2]'와 '[3]'은 같은 메모리 주소를 가리키며, 이는 'a'의 주소와 'pa'가 가리키는 주소가 같음을 의미합니다. '[4]'는 'pa' 자체의 메모리 주소를 나타냅니다. 이는 포인터 변수 또한 메모리 상의 주소를 가지고 있음을 보여줍니다.

이 코드에서는 다섯 가지 포인터 변수의 선언 및 사용 방식이 나타나 있습니다.

  1. 값형 변수: int a = 10;에서 'a'는 값형 변수로, 실제 값을 저장합니다. 여기서는 10이 저장되어 있습니다.

  2. 포인터 변수: int* pa;에서 'pa'는 포인터 변수로, 메모리 주소를 저장합니다.

  3. 포인터 변수에 주소 대입: pa = &a;에서 '&' 연산자를 사용하여 'a'의 주소를 'pa'에 대입합니다.

  4. 역참조를 사용한 값 대입: *pa = 20;에서 '*' 연산자를 사용하여 'pa'가 가리키는 주소에 20을 대입합니다. 이를 역참조라고 합니다.

  5. 포인터 변수 표현: 이후의 printf 구문들에서는 포인터 변수를 어떻게 표현하는지 보여줍니다.

이와 같이 C 언어에서 포인터를 사용하면 메모리에 직접 접근할 수 있으며, 이를 통해 더 효율적인 코드를 작성할 수 있습니다. 그러나 포인터를 사용할 때는 주의가 필요하며, 특히 메모리 해제에 주의해야 합니다. 메모리 누수는 시스템의 성능 저하를 일으킬 수 있으므로, 포인터를 사용할 때는 항상 주의해야 합니다.

포인터 시작 (C)

다음 동영상 강의는 제가 2007년 01월 10일 오프라인 강의했던 자료입니다. 오래 전 자료이지만 지금도 개념은 동일합니다.

자바캠퍼스 - C 언어 포인터 시작하기

코드: 01.포인터시작.c

// 포인터시작.c
// pointer-start-c.c 
#include <stdio.h>

int main(void)
{
    int a, b, c; // 값형 변수
    int* p; // 포인터(참조;주소)형 변수

    a = 100;
    b = 200;

    p = &c;

    c = a + b;

    printf("a : %d\n", a);
    printf("b : %d\n", b);
    printf("c : %d\n", c);
    printf("p : %p\n", p); // 번지
    printf("*p : %d\n", *p); // 참조하고 있는 공간의 값 == 역참조 

    return 0;
}

결과

 a: 100
 b: 200
 c: 300
 p: 00EFFE80
*p: 300

예제: 02.포인터연산.c

// 포인터연산.c
#include <stdio.h>

int main(void)
{
	// 값형 변수
	int a, b, c;
	// 포인터(참조)형 변수
	int* pa, * pb, * pc;

	a = 10;
	b = 20;
	c = 0;

	pa = &a;
	pb = &b;
	pc = &c;

	*pc = *pa + *pb;

	printf("%d, %d, %d\n", *pa, *pb, *pc);

	return 0; 
}

실행

10, 20, 30

예제: 03.정수형포인터.c

// 03.정수형포인터.c
#include <stdio.h>

int main(void)
{
    // 값형 변수
    int a = 365;
    // 참조형 변수
    int* pa = &a;

    printf("a : %d\n", a);//365
    printf("&a : %p\n", &a);//1244992
    //printf("*a : %d\n", *a);

    // pa에 들어있는 다른 변수(공간)의 번지 수
    printf("pa : %p\n", pa);//1244992번지
    // pa가 참조하고 있는 변수에 들어있는 값
    printf("*pa : %d\n", *pa);//365
    // pa 변수 자체의 고유 어드레스(주소)
    printf("&pa : %p\n", &pa);//1244980

    return 0;
}

실행

a : 365
&a : 00EBFBF4
pa : 00EBFBF4
*pa : 365
&pa : 00EBFBE8

포인터_실수형.c

/*
    7.4. 예제. 실수형 포인터 변수 선언 : 포인터_실수형.c
*/
#include <stdio.h>

int main(void)
{
    float a, b, c;
    float* p;

    a = 100.0, b = 200.0;
    p = &c;	// c의 주소를 p에 기억

    *p = a + b; // c = a + b
    printf("%.1f + %.1f = %.1f\n", a, b, *p);
    printf("%x %x %x %x %x\n", &a, &b, &c, p, &p);
}

//100.0 + 200.0 = 300.0
//12ff7c 12ff78 12ff74 12ff74 12ff70
100.0 + 200.0 = 300.0
eff780 eff774 eff768 eff768 eff75c

포인터 관련 오프라인 강의 소스 모음

01.포인터변수.c

#include <stdio.h>

int main(void)
{
    int a = 10;

    int* b;

    b = &a;//a주소 : 별칭

    printf("%d %d\n", a, *b);

    return 0;
}
10 10

포인터 변수의 값 가져오기

단항 연산자인 * 연산자를 사용하면 포인터가 가리키는 변수의 값을 가져옵니다. 단항 * 연산자를 포인터 간접 참조 연산자로 부릅니다.

* 역참조(dereference)

포인터 변수가 가리키는 곳의 실제 데이터를 가져올 때에는 * 기호를 포인터 변수에 붙여 값을 가져올 수 있습니다. 프로그래밍에서 포인터에 포함된 주소의 정보에 액세스하는 것을 역참조라고 합니다. 물론, 그냥 참조로 표현해도 됩니다.

정수형 포인터 변수를 선언 및 참조 후 사용하는 방법을 코드로 살펴보겠습니다.

코드: IntegerPointer.c

#include <stdio.h>

int main(void)
{
    // 정수형 변수 선언 및 초기화
    int width = 1920;
    int height = 1080;

    // 정수형 포인터 선언 및 참조
    int* w = &width;
    int* h = &height;

    // 역참조로 값 출력
    printf("FHD: %d X %d\n", *w, *h);

    // 역참조(dereference)로 값 설정
    *w = 3840;
    *h = 2160;

    // 역참조로 값 출력
    printf("UHD: %d X %d\n", *w, *h);

    return 0;
}
FHD: 1920 X 1080
UHD: 3840 X 2160

일반 변수와 포인터 변수

C 언어 포인터로 정수 형식을 참조하는 정수형 포인터를 소개합니다.

다음 코드를 그림으로 표현해 보겠습니다.

코드: pointer_integer.c

int a = 20, b = 35, c = 0; // 정수 변수 선언
int* pa, * pb, * pc; // 정수 참조 변수 선언

pa = &a; pb = &b; pc = &c; // 각각의 변수를 참조

*pc = *pa + *pb; // 연산

printf("%d + %d = %d\n", *pa, *pb, *pc); // 20 + 35 = 55

그림: 일반 변수를 참조하는 포인터 변수

일반 변수를 참조하는 포인터 변수

포인터_포인터변수.c

포인터_포인터변수.c

/*
    포인터 변수: 특정 변수의 주소를 담을 수 있는 그릇
*/
#include <stdio.h>

int main(void)
{
    int i, j, k; // 일반 변수 : 값
    int* p;		 // 포인터 변수 : 다른 변수의 주소, 레퍼런스(참조) 변수

    i = 10;
    j = 20;
    k = 0;

    p = &k;	// 변수 k의 주소를 p에 기억

    printf("%d %d %d %d\n", i, j, k, *p); // *p => 포인터 변수가 참조하고 있는 변수의 값

    *p = i * j; // k = i * j;

    printf("%d %d %d %d\n", i, j, k, *p);

    return 0;
}
10 20 0 0
10 20 200 200

포인터_정수형.c

pointer_integer.c, 포인터_정수형.c

#include <stdio.h>

int main(void)
{
    int i = 10;
    int j = 20;
    int k = 0;

    int* p1;
    int* p2;
    int* p3;

    // 각각의 일반변수를 포인터(참조형) 변수에 참조
    p1 = &i;
    p2 = &j;
    p3 = &k;

    *p3 = *p1 * *p2;

    printf("%d * %d = %d\n", *p1, *p2, *p3);

    return 0;
}
10 * 20 = 200

포인터_포인터변수의주소출력.c

pointer_variable_address.c, 포인터_포인터변수의주소출력.c

/*
	변수(일반, 포인터)의 주소 출력: &
*/
#include <stdio.h>

int main(void)
{
	int i;
	int* p;

	i = 100;
	p = &i;

	printf("일반변수 i의 값: %d\n", i); // 100
	printf("포인터변수 p가 참조하고 있는 변수의 값: %d\n", *p); // 100

	printf("일반변수 i의 주소값: %u\n", &i); // %u(unsigned): 부호없는 정수
	printf("포인터변수 p의 주소값: %u\n", &p); // 
	printf("포인터변수 p의 값: %u\n", p); // i변수의 주솟값과 동일

	return 0;
}
일반변수 i의 값: 100
포인터변수 p가 참조하고 있는 변수의 값: 100
일반변수 i의 주소값: 2707094068
포인터변수 p의 주소값: 2707094104
포인터변수 p의 값: 2707094068

C 언어 포인터 변수 값 출력 방법 3가지

코드: 03.포인터변수의주소.c

#include <stdio.h>

int main(void)
{
    int a, * pa;

    a = 10;
    pa = &a;

    //[1] 값형 a변수
    printf("%d\n", a);//a의 값
    printf("%d\n", &a);//a의 메모리상의 주소 : 15727580
    //[2] 포인터형 pa변수
    printf("%d\n", pa);//pa변수의 실제값:a의주소값 : 15727580
    printf("%d\n", &pa);//pa변수의 주소:15727568
    printf("%d\n", *pa);//pa변수가참조하는곳의실제데이터

    return 0;
}

실행

10
15727580
15727580
15727568
10

강의

동영상 강의: C 언어_포인터_03_포인터변수의주소

포인터_실수형포인터.c

pointer_float_type.c, 포인터_실수형포인터.c

#include <stdio.h>

int main(void)
{
    float i;
    float* p; // 특정 주소(번지;Address)값을 담을 수 있는 그릇

    i = 100.0;
    p = &i;

    printf("p가 참조하고 있는 곳의 값: %.2f\n", *p);
    printf("p의 주소 값: %#x\n", &p); // 0xFFFFFF
    printf("p의 자체 값: %#x\n", p); // 변수 i의 주소값
    printf("i의 주소 값: %#x\n", &i); // 변수 i의 주소값

    return 0;
}

실행 결과의 주솟값은 실행 시점의 상황에 따라 다르게 나오는게 정상입니다.

p가 참조하고 있는 곳의 값: 100.00
p의 주소 값: 0x2daffb88
p의 자체 값: 0x2daffb64
i의 주소 값: 0x2daffb64
p가 참조하고 있는 곳의 값: 100.00
p의 주소 값: 0x8a93fad8
p의 자체 값: 0x8a93fab4
i의 주소 값: 0x8a93fab4

값형 변수와 참조형 변수

C 언어에서 변수 선언할 때 값형 변수와 참조형 변수로 구분할 수 있습니다. 참조형 변수가 포인터입니다.

코드: pointer_variable_five.c

// pointer_variable_five.c*** ```C
/*
	값(Value)형 변수/배열
	포인터(참조;Reference)형 변수/배열
*/
#include <stdio.h>

int main(void)
{
	// Value Type 변수
	int a = 10;

	// Reference Type 변수: 포인터 변수
	int* pa;

	// 참조 추가
	pa = &a; // 포인터 변수에 주소 대입

	*pa = 20; // 역참조를 사용하여 포인터 변수가 참조하는 변수에 값 대입

	// 값형 변수 표현
	printf("[1] %d\n", a); // 20
	printf("[2] %p\n", &a); // 000000FFC78FFC24 번지: 계속 바뀌는 부분

	// 포인터형 변수 표현
	printf("[3] %p\n", pa); // 000000FFC78FFC24 번지
	printf("[4] %p\n", &pa); // 000000FFC78FFC48 번지
	printf("[5] %d\n", *pa); // 20

	return 0; 
}
[1] 20
[2] 000000FFC78FFC24
[3] 000000FFC78FFC24
[4] 000000FFC78FFC48
[5] 20

[2]번과 [3]번은 같은 주솟값이 나오고 [4]번 출력 결과는 이웃된 근처의 주솟값이 나옵니다. 이 주솟값은 계속 바뀌는 데이터입니다.

C 언어 포인터 - 값형 변수와 참조형 변수의 차이

동영상 강의: 값형 변수와 참조형 변수의 차이

예제: valuetype-referencetype.c

// valuetype-referencetype.c
// 값형 변수와 참조형 변수의 차이 
#include <stdio.h>

void plus(int* p) // 매개 변수로 포인터를 받음 
{
    *p = *p + 10; // 역참조에 의해서 main 함수의 i 변수 값을 증가
    printf("*p = %d\n", *p);
}

int main(void)
{
    int i = 10; // main과 plus에서 데이터 저장 공간으로 사용
    int j = 20; // 값형 변수 선언과 동시에 초기화

    int* p; // 포인터 변수 선언 
    p = &i; // 참조형 변수에 값형 변수 참조 

    plus(p); // 넘겨준 포인터 변수의 값을 10 증가 

    printf("i : %d, j = %d\n", i, j);

    return 0; 
}

실행

plus function: *p: 20
main function: i: 20, j: 20, *p: 20

포인터 변수는 값형 변수를 참조

포인터 변수는 참조형 변수로도 불립니다. 포인터 변수는 변수 위치에 직접 값이 저장되는 값형 변수를 참조합니다.

먼저 다음 샘플 코드를 살펴보세요.

// pointer_reference.c
int v = 10; // 값형 변수
int* p = &v; // 참조형(포인터) 변수

위 코드를 그림으로 표현해보면 다음과 같습니다.

[그림] 참조형 변수는 값형 변수를 참조

참조형 변수는 값형 변수를 참조

코드

// pointer_reference.c
// 포인터 변수는 값형 변수를 참조 
#include <stdio.h>

int main(void)
{
    int v = 10; // 값형 변수
    int* p = &v; // 참조형(포인터) 변수 
    printf("%d\n", *p); // 10
}

실행

10

값 형 변수와 참조형 변수의 차이

코드: valuetype-referencetype.c

// valuetype-referencetype.c
// 값형 변수와 참조형 변수의 차이 
#include <stdio.h>

void plus(int* p) // 매개 변수로 포인터를 받음 
{
    *p = *p + 10; // 역참조에 의해서 main 함수의 i 변수 값을 증가
    printf("plus function: *p: %d\n", *p);
}

int main(void)
{
    int i = 10; // main과 plus에서 데이터 저장 공간으로 사용
    int j = 20; // 값형 변수 선언과 동시에 초기화

    int* p; // 포인터 변수 선언 
    p = &i; // 참조형 변수에 값형 변수 참조 

    plus(p); // 넘겨준 포인터 변수의 값을 10 증가 

    printf("main function: i: %d, j: %d, *p: %d\n", i, j, *p);

    return 0; 
}
plus function: *p: 20
main function: i: 20, j: 20, *p: 20

정수 포인터와 실수 포인터

포인터는 이미 있는 정수 포인터와 실수 포인터를 참조해서 사용할 수 있습니다.

코드: NumberPointer.c

#include <stdio.h>

int main(void)
{
    int age = 21;
    const float PI = 3.14F;

    int* p1 = &age;
    float* p2 = &PI;

    printf("변수 age 값: %d\n", age);
    printf("변수 age 주소: %p\n", &age);

    printf("포인터 변수 p1의 값: %p\n", p1);
    printf("포인터 변수 p1의 참조 값(역참조): %d\n", *p1);
    printf("포인터 변수 p1의 주소: %p\n", &p1);

    printf("\n");

    printf("상수 PI 값: %.2f\n", PI);
    printf("상수 PI 주소: %p\n", &PI);

    printf("포인터 변수 p2의 값: %p\n", p2);
    printf("포인터 변수 p2의 참조 값(역참조): %.2f\n", *p2);
    printf("포인터 변수 p2의 주소: %p\n", &p2);

    return 0;
}
변수 age 값: 21
변수 age 주소: 00F6FE5C
포인터 변수 p1의 값: 00F6FE5C
포인터 변수 p1의 참조 값(역참조): 21
포인터 변수 p1의 주소: 00F6FE44

상수 PI 값: 3.14
상수 PI 주소: 00F6FE50
포인터 변수 p2의 값: 00F6FE50
포인터 변수 p2의 참조 값(역참조): 3.14
포인터 변수 p2의 주소: 00F6FE38

정수 포인터와 실수 포인터

현재 예제의 실행 결과의 주소값은 매번 달라집니다. 포인터 변수에는 참조하고 있는 변수의 주솟값이 저장됩니다. 포인터 변수 p1의 주소를 &p1 형태로 출력해보면 변수 age의 주소가 나오는 것을 확인할 수 있습니다. 이러한 포인터에 들어 있는 값 자체를 얻고자할 때에는 * 기호를 사용하여 역참조하면 됩니다.

널 포인터 사용하기

NULL 키워드를 사용하는 널 포인터(null pointer)는 단어 그대로 아무것도 가리키지 않는 상태를 의미합니다.

포인터 변수에 NULL로 초기화하면 메모리가 할당되지 않은 상태입니다.

코드: null_pointer.c



특정 포인터 변수가 NULL인지를 비교해서 널 포인터라면 새롭게 메모리를 할당하는 식으로 사용될 수 있습니다.

포인터 변수 주소 증가 또는 감소

  • *p : 역참조로 포인터 변수가 가리키는 곳의 값을 반환
  • *p++ : *p가 가리키는 곳의 값을 1 증가
  • *++p : p를 1증가(포인터 주소를 다음 주소로 증가 후)한 다음에 그곳의 값을 가져오기
  • ++*p : *p가 가리키는 곳의 값을 가져와서 1증가해서 반환
  • (*p)++ : *p가 가리키는 곳의 값을 가져와서 사용 후 나중에 1증가

포인터로 문자열 저장하기

C 언어에서는 문자 포인터를 사용해서 문자열을 저장할 수 있습니다. 문자열을 저장할 때에는 포인터 변수 선언과 동시에 문자열 리터럴을 초기화하거나 선언 따로 초기화 따로를 할 수 있습니다.

코드: StringPointer.c

#include <stdio.h>

int main(void)
{
    //[1] 문자열 선언과 동시에 초기화
    char* name = "김서울";

    //[2] 문자열 포인터 선언 후 초기화 
    char* address;
    address = "SEOUL"; // 문자열(문자 배열)의 시작 주소를 포인터에 저장 

    // 전체 출력
    printf("%s - %s\n", name, address);

    // 부분 출력(인덱스 이후로 출력): 한글은 2byte 문자
    printf("%s - %s\n", (name + 2), (address + 3));

    return 0;
}
김서울 - SEOUL
서울 – UL

코드에서처럼 문자열 포인터 선언 후 따로 초기화할 때에는 *address가 아닌 address에 문자열 리터럴을 할당해야 합니다. 왜냐하면 문자열 리터럴은 그 자체가 배열의 시작 주소를 담고 있기 때문입니다.

포인터_문자열입출력.c***

코드: 포인터_문자열입출력.c

#define _CRT_SECURE_NO_WARNINGS 
#include <stdio.h>

int main(void)
{
	//char* s;
	char s[10];

	//s = "안녕";
	scanf("%s", s);

	printf("%s\n", s);

	return 0; 
}
안녕
안녕

포인터로 문자열 저장하고 출력하기

pointer_array_string.c, 포인터배열_문자열.c

/*
	포인터를 사용하여 문자배열(문자열)을 저장하기
*/
#include <stdio.h>

int main(void)
{
	char c;
	char* p;

	c = 'A';
	p = "Hello World"; // "Hello World\0"

	printf("%c\n", c); // A
	printf("%s\n", p); // Hello World

	return 0;
}
A
Hello World

C 언어 포인터 전체 복습 설명

C 언어 포인터에 대한 전체 복습 강좌입니다.

강의

https://youtu.be/gbq9-8xOpXE

코드: 포인터설명_참조형.c

// 포인터설명_참조형.c
// 포인터(Pointer) : 참조형
#include <stdio.h>

void main()
{
	// 값형(Value Type) 변수 : 해당 변수 이름공간에 데이터 저장
	int a;
	a = 10;
	// 포인터형(Pointer Type;Reference Type) 변수 : 
	// 다른 변수의 공간을 참조(가리키는)하는 형태
	int* p;
	p = &a;//a변수의 주소(Address)를 포인터형 변수에 기록
	// 출력
	printf("a의 값 : %d\n", a);//10
	printf("a의 주소 : %d\n", &a);//1244992

	printf("p의 값 : %d\n", p);//1244992
	printf("p가 가리키는 값 : %d", *p);//10
	printf("p의 주소 : %d\n", &p);//1244992+-???=1244980
}

실행

a의 값 : 10
a의 주소 : 20184744
p의 값 : 20184744
p가 가리키는 값 : 10p의 주소 : 20184732

코드: 포인터배열설명.c

// 포인터배열설명.c
// 포인터 배열
#include <stdio.h>

void main()
{
	int i;
	int a[] = { 2, 1, 5, 4, 3 };

	// 포인터 형 변수 선언
	int* p;

	p = a;//&a[0];//배열명 자체가 배열의 시작 주소

	for (i = 0; i < 5; i++)
	{
		printf("배열: %d, 포인터: %d\n", a[i], *(p + i));
	}
}

실행

배열: 2, 포인터: 2
배열: 1, 포인터: 1
배열: 5, 포인터: 5
배열: 4, 포인터: 4
배열: 3, 포인터: 3

코드: 포인터_문자열처리설명.c

// 포인터_문자열처리설명.c
#include <stdio.h>

void main()
{
    char c = 'A';	// 값형
    char* s; // string s; // 참조형

    s = "안녕하\0세요.";

    printf("%c %s\n", c, s);
    printf("%s\n", s + 7);//영문1Byte, 한글2Byte
}

실행

A 안녕하
세요.

코드: 포인터_포인터배열설명.c

// 포인터_포인터배열설명.c
// 포인터 배열
#include <stdio.h>

void main()
{
    char* s[3];

    s[0] = "AA";
    s[1] = "BB";
    s[2] = "CC";

    printf("%s %s %s\n", s[0], s[1], s[2]);
}

실행

AA BB CC

11 포인터를 사용한 문자열 출력 제어

pointer_array_string_literal.c, 포인터배열_문자열출력.c

// 포인터를 사용한 문자열 출력 제어
#include <stdio.h>

int main(void)
{
    // C언어: 문자열 처리 키워드: X, Java/C#: String 키워드
    //string s = "안녕하세요...";
    char* str;

    str = "안녕하세요. 반갑습니다."; // 한글/일어/중국어: 2byte코드, 영어: 1byte코드

    printf("%s\n", str);		// 전체 출력
    printf("%s\n", str + 12);	// 반갑습니다.
    printf("%s\n", str + 3);	// '녕'을 반쪽만 출력하다보니 깨져서 보여짐

    str = "상수에 값을 덮어쓰기"; // 허용, scanf() 접근 불가
    printf("%s\n", str);		// 전체 출력

    return 0;
}
안녕하세요. 반갑습니다.
반갑습니다.
聆究셀? 반갑습니다.
상수에 값을 덮어쓰기

포인터로 문자열 배열 선언하기

C 언어에서 문자열 형식을 만들 수 있는 char* 데이터 타입으로 하나의 이상의 문자열을 담을 수 있는 문자열 배열을 선언할 수 있습니다. 문자열 배열은 선언 후 따로 각각의 요소를 문자열 리터럴로 저장할 수 있습니다.

코드: StringArray.c

#include <stdio.h>

int main(void)
{
    char* languages[3]; // 문자열 배열 선언

    languages[0] = "C";
    languages[1] = "C++";
    languages[2] = "C#";

    for (int i = 0; i < 3; i++)
    {
        printf("%s\n", languages[i]);
    }

    return 0;
}
C
C++
C#

문자열 리터럴은 그 자체가 첫 번째 문자의 시작 주소를 담고 있기에 포인터 배열의 각 요소에 대입되어 사용할 수 있습니다.

포인터로 배열의 배열 표현하기

pointer_array_zigzag.c, 포인터배열_배열의배열.c

// 배열의 배열
#include <stdio.h>

int main(void)
{
    char* arr[3]; // 기본: 문자 3개 저장, 포인터 배열: 문자열 3개를 저장해 놓을 그릇

    arr[0] = "C언어";
    arr[1] = "C++";
    arr[2] = "C#";

    for (int i = 0; i < 3; i++)
    {
        printf("%s \n", arr[i]);
    }

    return 0;
}
C언어
C++
C#

gets 함수로 문자열 입력

pointer_array_gets.c, 포인터배열_문자열입력.c

// 화면으로부터 문자열을 입력받아서 출력하는 프로그램: scanf() 대신에 gets() 함수 사용
#include <stdio.h>

int main(void)
{
	char str[100];

	printf("문자열을 입력하시오 : ");
	//scanf("%s", str);
	gets(str); // 한줄의 문자열을 읽어오는 함수

	printf("%s\n", str);

	return 0;
}
문자열을 입력하시오 : C Language
C Language

포인터를 사용한 기초 알고리즘

포인터를 사용하여 최댓값 구하기

algorithm_pointer_max.c, 알고리즘_포인터_최댓값.c

// 최대값을 구하는 알고리즘
#include <stdio.h>
#include <limits.h> // INT_MIN, INT_MAX

void main(void)
{
    //[1] Input
    int intNum[] = { -3, -2, -1, -4, -5 };
    int intMax = INT_MIN;
    int i;
    int* p; p = intNum; // &intNum[0];

    //[2] Process: MAX
    for (i = 0; i < 5; i++)
    {
        if (intMax < *(p + i))
        {
            intMax = *(p + i);
        }
    }

    //[3] Output
    printf("최대값: %d\n", intMax);

    return 0;
}
최대값: -1

포인터를 사용하여 배열 정렬하기

algorithm_pointer_selectionsort.c, 알고리즘_포인터_선택정렬.c

#include <stdio.h>

// 포인터를 사용한 선택 정렬 함수
void SelectionSort(int* p)
{
    int temp;
    for (int i = 0; i < 5 - 1; i++) {
        for (int j = i + 1; j < 5; j++) {
            if (*(p + i) > *(p + j)) {
                temp = *(p + i);
                *(p + i) = *(p + j);
                *(p + j) = temp;
            }
        }
    }
}

int main(void)
{
    //[1] Input
    int intNum[5] = { 3, 2, 1, 5, 4 };
    int temp;

    //[2] Process: 정렬알고리즘(선택, 버블, 퀵, ...)
    SelectionSort(intNum);
    //for (int i = 0; i < 5 - 1; i++)
    //{
    //    for (int j = i + 1; j < 5; j++)
    //    {
    //        if (intNum[i] > intNum[j])
    //        {
    //            temp = intNum[i];
    //            intNum[i] = intNum[j];
    //            intNum[j] = temp;
    //        }
    //    }
    //}

    //[3] Output
    for (int i = 0; i < 5; i++)
    {
        printf("%d\n", intNum[i]);
    }

    return 0;
}
1
2
3
4
5

값 형식(Value Type)과 참조 형식(Reference Type)

클래스와 구조체와 같은 데이터 타입을 말할 때 값 형식(Value Type)과 참조 형식(Reference Type)으로 구분짓기도 합니다.

  • 값 형식: 지금까지 다룬 int, double과 같은 데이터 구조는 내부적으로 구조체로 되어 있고 전형적인 값 형식의 데이터 구조입니다.
  • 참조 형식: 모든 클래스의 부모 역할을 하는 최상위 클래스인 Object 클래스와 이를 나타내는 object 키워드는 전형적인 참조 형식의 데이터 구조입니다.

값 형식(Value Type)

값 형식은 개체에 값 자체를 담고 있는 구조입니다.

그림: 값 형식

값 형식

참조 형식(Reference Type)

참조 형식은 개체가 값을 담고 있는 또 다른 개체를 포인터로 바라보는 구조입니다. 여러 값들이 동일한 개체를 가리킬 수 있습니다.

그림: 참조 형식

값 형식

멤버 액세스 및 요소 액세스 연산자

포인터에서 사용할 수 있는 연산자에는 -> (멤버 액세스) 연산자와 [] (요소 액세스) 연산자가 있습니다.

퀴즈

다음 중 동적으로 메모리를 할당하는 방법을 고르세요.

a. int *p = malloc(80);

정답: a

TODO:

포인터 관련 연산자

다음 연산자를 사용하여 포인터로 작업할 수 있습니다.

-

  • 산술 연산자: +, -, ++--
  • 비교 연산자: ==, !=, <, >, <=>=
NOTE

포인터를 사용한 작업에는 안전하지 않은 컨텍스트가 필요합니다. 안전하지 않은 블록을 포함하는 코드는 AllowUnsafeBlocks 컴파일러 옵션을 사용하여 컴파일해야 합니다.

Address-of 연산자 &

단항 & 연산자는 해당 피연산자의 주소를 반환합니다.

unsafe
{
    int number = 27;
    int* pointerToNumber = &number;

    Console.WriteLine($"Value of the variable: {number}");
    Console.WriteLine($"Address of the variable: {(long)pointerToNumber:X}");
}
// Output is similar to:
// Value of the variable: 27
// Address of the variable: 6C1457DBD4

& 연산자의 피연산자는 고정 변수여야 합니다. 고정 변수는 가비지 수집기의 작동에 영향을 받지 않는 스토리지 위치에 있는 변수입니다. 앞의 예제에서 로컬 변수 number는 스택에 있으므로 고정 변수입니다. 가비지 수집기에 의해 영향을 받을 수 있는 스토리지 위치에 상주하는 변수(예: 재배치됨)를 이동 가능한 변수라고 합니다. 개체 필드 및 배열 요소는 이동 가능한 변수의 예입니다. fixed 문으로 "fix" 또는 "pin"으로 할 경우 이동 가능한 변수의 주소를 가져올 수 있습니다. 가져온 주소는 fixed 문 블록 내에서만 유효합니다. 다음 예제에서는 fixed 문과 & 연산자를 사용하는 방법을 보여줍니다.

unsafe
{
    byte[] bytes = { 1, 2, 3 };
    fixed (byte* pointerToFirst = &bytes[0])
    {
        // The address stored in pointerToFirst
        // is valid only inside this fixed statement block.
    }
}

변수, 배열과 달리 상수 또는 리터럴 값의 주소를 가져올 수 없습니다.

포인터 간접 참조 연산자 *

단항 포인터 간접 참조 연산자 *는 피연산자가 가리키는 변수를 가져옵니다. 역참조 연산자라고도 합니다. * 연산자의 피연산자는 포인터 형식이여야 합니다.

PointerIndirection.cs

unsafe
{
    char letter = 'A';
    char* pointerToLetter = &letter;
    Console.WriteLine($"Value of the `letter` variable: {letter}");
    Console.WriteLine($"Address of the `letter` variable: {(long)pointerToLetter:X}");

    *pointerToLetter = 'Z';
    Console.WriteLine($"Value of the `letter` variable after update: {letter}");
}
// Output is similar to:
// Value of the `letter` variable: A
// Address of the `letter` variable: DCB977DDF4
// Value of the `letter` variable after update: Z

* 연산자를 void* 유형의 식에 적용할 수 없습니다.

산술 연산자 학습을 통해서 알고 있는 단항 연산자가 아닌 이항 연산자로서 사용되는 * 연산자는 해당 숫자 피연산자의 곱을 컴퓨팅합니다.

포인터 멤버 액세스 연산자 ->

화살표 연산자로 불리는 -> 연산자는 포인터 간접 참조멤버 액세스를 결합합니다. 즉, xT* 형식의 포인터이고 yT의 액세스 가능한 멤버인 경우 다음과 같이 사용될 수 있습니다.

x->y

위의 식은 아래의 삭과 동일합니다.

(*x).y

다음 예제에서는 -> 연산자의 사용법을 보여 줍니다.

public struct Coords
{
    public int X;
    public int Y;
    public override string ToString() => $"({X}, {Y})";
}

public class PointerMemberAccessExample
{
    public static unsafe void Main()
    {
        Coords coords;
        Coords* p = &coords;
        p->X = 3;
        p->Y = 4;
        Console.WriteLine(p->ToString());  // output: (3, 4)
    }
}

You cannot apply the -> operator to an expression of type void*.

-> 연산자를 void* 유형의 식에 적용할 수 없습니다.

포인터 요소 액세스 연산자 []

포인터 형식의 식 p의 경우 p[n] 양식의 포인터 요소 액세스는 *(p + n)으로 평가됩니다. 여기서 n은 암시적으로 int, uint, long 또는 ulong으로 전환할 수 있는 형식이어야 합니다.

다음 예제에서는 포인터 및 [] 연산자를 사용하여 배열 요소에 액세스하는 방법을 보여 줍니다.

unsafe
{
    char* pointerToChars = stackalloc char[123];

    for (int i = 65; i < 123; i++)
    {
        pointerToChars[i] = (char)i;
    }

    Console.Write("Uppercase letters: ");
    for (int i = 65; i < 91; i++)
    {
        Console.Write(pointerToChars[i]);
    }
}
// Output:
// Uppercase letters: ABCDEFGHIJKLMNOPQRSTUVWXYZ

앞의 예제에서 stackalloc 식은 스택에 메모리 블록을 할당합니다.

NOTE

포인터 요소 액세스 연산자는 범위 이탈 오류를 검사하지 않습니다.

void* 형식의 식으로 포인터 요소 액세스에 [] 형태를 사용할 수 없습니다.

배열 요소 또는 인덱서 액세스에 대해 [] 연산자를 사용할 수도 있습니다.

포인터 산술 연산자

포인터를 사용하여 다음과 같은 산술 연산을 수행할 수 있습니다.

  • 포인터에서 정수 값 추가 또는 빼기
  • 두 포인터 빼기
  • 포인터 증가 또는 감소

void* 형식의 포인터를 사용하여 이러한 작업을 수행할 수 없습니다.

포인터에서 정수 값 더하기 또는 빼기

T* 형식의 포인터 pint, uint, long 또는 ulong으로 암시적으로 변환할 수 있는 형식의 n 식에 대한 더하기와 빼기가 다음과 같이 정의됩니다.

  • p + nn + p 식 모두 p에서 지정한 주소에 n * sizeof(T)를 추가한 결과 T* 유형의 포인터를 생성합니다.
  • p - n 식은 p에 의해 지정된 주소에서 n * sizeof(T)를 뺀 결과 T* 유형의 포인터를 생성합니다.

sizeof 연산자는 바이트 단위의 형식 크기를 가져옵니다.

다음 예제에서는 포인터로 + 연산자를 사용하는 방법을 보여줍니다.

unsafe
{
    const int Count = 3;
    int[] numbers = new int[Count] { 10, 20, 30 };
    fixed (int* pointerToFirst = &numbers[0])
    {
        int* pointerToLast = pointerToFirst + (Count - 1);

        Console.WriteLine($"Value {*pointerToFirst} at address {(long)pointerToFirst}");
        Console.WriteLine($"Value {*pointerToLast} at address {(long)pointerToLast}");
    }
}
// Output is similar to:
// Value 10 at address 1818345918136
// Value 30 at address 1818345918144

포인터 빼기

T* 형식의 두 가지 포인터 p1p2에 대해 p1 - p2 식은 p1p2에 의해 지정된 주소 간의 차이를 sizeof(T)로 나누어 생성합니다. 결과의 형식은 long입니다. 즉, p1 - p2((long)(p1) - (long)(p2)) / sizeof(T)로 계산됩니다.

다음 예제에서는 포인터 빼기를 보여줍니다.

unsafe
{
    int* numbers = stackalloc int[] { 0, 1, 2, 3, 4, 5 };
    int* p1 = &numbers[1];
    int* p2 = &numbers[5];
    Console.WriteLine(p2 - p1);  // output: 4
}

포인터 증가 및 감소

++ 증가 연산자는 포인터 피연산자에 1을 추가합니다. -- 감소 연산자는 포인터 피연산자에서 1을 뺍니다.

두 연산자는 접미사(p++p--)와 접두사(++p--p)의 두 가지 형태로 지원됩니다. p++p--는 해당 라인에서의 실행 이전p 값입니다. ++p--p는 해당 라인에서의 실행 이후의 값입니다. 이 내용은 우리가 앞서 증감 연산자 파트에서 학습한 내용과 동일합니다.

The following example demonstrates the behavior of both postfix and prefix increment operators: 다음 예제에서는 접미사 및 접두사 증가 연산자의 동작을 보여줍니다.

unsafe
{
    int* numbers = stackalloc int[] { 0, 1, 2 };
    int* p1 = &numbers[0];
    int* p2 = p1;
    Console.WriteLine($"Before operation: p1 - {(long)p1}, p2 - {(long)p2}");
    Console.WriteLine($"Postfix increment of p1: {(long)(p1++)}");
    Console.WriteLine($"Prefix increment of p2: {(long)(++p2)}");
    Console.WriteLine($"After operation: p1 - {(long)p1}, p2 - {(long)p2}");
}
// Output is similar to
// Before operation: p1 - 816489946512, p2 - 816489946512
// Postfix increment of p1: 816489946512
// Prefix increment of p2: 816489946516
// After operation: p1 - 816489946516, p2 - 816489946516

포인터 비교 연산자

==, !=, <, >, <=>= 연산자를 사용하여 void*를 포함한 모든 포인터 형식의 피연산자를 비교할 수 있습니다. 이러한 연산자는 두 피연산자가 지정한 주소를 부호 없는 정수인 것처럼 비교합니다.

연산자 우선 순위

다음 목록에서는 포인터 관련 연산자를 가장 높은 우선순위부터 가장 낮은 것으로 정렬합니다.

  • 접미사 증가 x++ 및 감소 x-- 연산자와 ->[] 연산자
  • 접두사 증가 ++x 및 감소 --x 연산자와 &* 연산자
  • 가감 연산자인 +- 연산자
  • 비교 연산자인 <, >, <=>= 연산자
  • 같음 ==!= 연산자

괄호 연산자인 ()를 사용하여 연산자 우선순위에 따라 주어진 평가 순서를 변경합니다.

연산자 오버로드 가능성

사용자 정의 형식은 포인터 관련 연산자 &, *, ->[](을)를 오버로드할 수 없습니다.

안전하지 않은 코드, 포인터 형식 및 함수 포인터

NOTE

현재 문서는 다음 경로의 Microsoft Learn 자료를 번역했습니다. Unsafe code, pointer types, and function pointers. 이 문서에 대한 모든 저작권은 마이크로소프트에 있으며 요청이 있을 경우 언제라도 게시가 중단될 수 있습니다. 번역 내용에 오역이 존재할 수 있고 주석은 번역자 개인의 의견일 뿐이며 마이크로소프트는 이에 관한 어떠한 보장도 하지 않습니다. 번역이 완료된 이후에도 대상 제품 및 기술이 개선되거나 변경됨에 따라 원문의 내용도 변경되거나 보완되었을 수 있으므로 주의하시기 바랍니다.

작성하는 대부분의 C# 코드는 "확인 가능한 안전한 코드"입니다. 확인 가능한 안전한 코드는 .NET 도구가 코드가 안전한지 확인할 수 있음을 의미합니다. 일반적으로 안전한 코드는 포인터를 사용하여 메모리에 직접 액세스하지 않습니다. 또한 원시 메모리를 할당하지 않습니다. 대신 관리형 개체를 만듭니다.

C#은 unsafe 컨텍스트를 지원하는데, 이 컨텍스트에서는 확인할 수 없는 코드를 작성할 수 있습니다. unsafe 컨텍스트에서 코드는 포인터를 사용하고, 메모리 블록을 할당 및 해제하고, 함수 포인터를 사용하여 메서드를 호출할 수 있습니다. C#의 안전하지 않은 코드가 반드시 위험한 것은 아닙니다. 단지 CLR에서 안전을 확인할 수 없을 뿐입니다.

안전하지 않은 코드에는 다음과 같은 속성이 있습니다.

  • 메서드, 형식 및 코드 블록은 안전하지 않은 것으로 정의할 수 있습니다.
  • 경우에 따라, 안전하지 않은 코드는 배열 범위 검사를 제거하여 애플리케이션의 성능을 향상할 수 있습니다.
  • 포인터가 필요한 네이티브 함수를 호출하는 경우 안전하지 않은 코드가 필요합니다.
  • 안전하지 않은 코드를 사용하면 보안 및 안정성 위험이 발생합니다.
  • 안전하지 않은 블록을 포함하는 코드는 AllowUnsafeBlocks 컴파일러 옵션을 사용하여 컴파일해야 합니다.

포인터 형식

안전하지 않은 컨텍스트에서, 형식은 포인터 형식일 수도 있고 값 형식 또는 참조 형식일 수도 있습니다. 포인터 형식 선언은 다음 형식 중 하나를 사용합니다.

type* identifier;
void* identifier; // 사용 가능하나 권장하지 않음

포인터 형식에서 * 앞에 지정된 형식을 참조 형식이라고 합니다. 비관리형 형식만 참조 형식일 수 있습니다.

포인터 형식은 개체에서 상속되지 않으며 포인터 형식과 object는 서로 변환되지 않습니다. 또한 박싱과 언박싱은 포인터를 지원하지 않습니다. 그러나 다른 포인터 형식 간의 변환 및 포인터 형식과 정수 형식 사이의 변환은 허용됩니다.

When you declare multiple pointers in the same declaration, you write the asterisk (*) together with the underlying type only. It isn't used as a prefix to each pointer name. For example:

동일한 선언에서 여러 포인터를 선언하는 경우 별표(*)는 기본 형식에만 함께 사용됩니다. 각 포인터 이름의 접두사로는 사용되지 않습니다. 예를 들면 다음과 같습니다.

int* p1, p2, p3;   // 허용
int *p1, *p2, *p3;   // C#에선 불가능

개체 참조는 포인터가 해당 개체 참조를 가리키는 경우에도 가비지 수집될 수 있으므로 포인터는 참조나 참조가 들어 있는 구조체를 가리킬 수 없습니다. 가비지 수집기는 포인터 형식에서 개체를 가리키는지 여부를 추적하지 않습니다.

MyType* 형식의 포인터 변수 값은 MyType 형식의 변수 주소입니다. 다음은 포인터 형식 선언의 예제입니다.

  • int* p: p는 정수에 대한 포인터입니다.
  • int** p: p는 정수에 대한 포인터를 가리키는 포인터입니다.
  • int*[] p: p는 정수에 대한 포인터의 1차원 배열입니다.
  • char* p: p는 문자에 대한 포인터입니다.
  • void* p: p는 알 수 없는 형식에 대한 포인터입니다.

포인터 간접 참조 연산자 *를 사용하면 포인터 변수가 가리키는 위치의 내용에 액세스할 수 있습니다. 예를 들어, 다음 선언을 참조하십시오.

int* myVariable;
...
*myVariable

*myVariable 식을 사용하면 myVariable 변수가 가리키는 곳의 int 형식의 값을 가져옵니다.

fixed 문에 대한 문서에는 몇 가지 포인터 예제가 있습니다. 다음 예제는 unsafe 키워드 및 fixed 문을 사용하고 정수 포인터를 증분하는 방법을 보여줍니다. 이 코드를 실행하려면 콘솔 애플리케이션의 Main 메서드에 붙여 넣습니다. 이러한 예제는 AllowUnsafeBlocks 컴파일러 옵션으로 컴파일되어야 합니다.

void* 형식의 포인터에는 간접 참조 연산자를 적용할 수 없습니다. 그러나 캐스트를 사용하여 void 포인터를 다른 포인터 형식으로 변환하거나 반대로 변환할 수 있습니다.

포인터는 null일 수 있습니다. null 포인터에 간접 참조 연산자를 적용할 때 발생하는 동작은 구현에 따라 다릅니다.

메서드 사이에 포인터를 전달하면 정의되지 않은 동작이 발생할 수 있습니다. in, out 또는 ref 매개 변수를 통해, 또는 함수 결과로 지역 변수에 포인터를 반환하는 메서드를 고려합니다. fixed 블록에서 포인터가 설정되면 이 포인터가 가리키는 변수의 고정 상태가 해제될 수 있습니다.

다음 표에서는 안전하지 않은 컨텍스트에서 포인터에 대해 수행할 수 있는 연산자와 문을 보여 줍니다.

연산자/문 설명
* 포인터 간접 참조를 수행합니다.
-> 포인터를 통해 구조체 멤버에 액세스합니다.
[] 포인터를 인덱싱합니다.
& 변수 주소를 가져옵니다.
++-- 포인터를 증가 및 감소시킵니다.
+- 포인터 산술 연산을 수행합니다.
==, !=, <, >, <=>= 포인터를 비교합니다.
stackalloc 스택에 메모리를 할당합니다.
fixed 해당 주소를 찾을 수 있도록 임시로 변수를 고정합니다.

모든 포인터 형식을 암시적으로 void* 형식으로 변환할 수 있습니다. 모든 포인터 형식에 값 null을 할당할 수 있습니다. 캐스트 식을 사용하여 모든 포인터 형식을 명시적으로 다른 포인터 형식으로 변환할 수 있습니다. 정수 형식을 포인터 형식으로 변환하거나, 포인터 형식을 정수 형식으로 변환할 수도 있습니다. 이 변환에는 명시적 캐스트가 필요합니다.

다음 예제에서는 int*byte*로 변환합니다. 포인터는 변수의 최하위 주소 지정 바이트를 가리킵니다. int 크기(4바이트)까지 결과를 연속적으로 증가할 경우 변수의 나머지 바이트를 표시할 수 있습니다.

고정 크기 버퍼

fixed 키워드를 사용하여 데이터 구조에 고정 크기 배열이 있는 버퍼를 만들 수 있습니다. 고정 크기 버퍼는 다른 언어 또는 플랫폼의 데이터 원본과 상호 운용되는 메서드를 작성할 때 유용합니다. 고정 크기 버퍼는 일반 구조체 멤버에 허용되는 모든 특성 또는 한정자를 사용할 수 있습니다. 배열 형식이 bool, byte, char, short, int, long, sbyte, ushort, uint, ulong, float, 또는 double이어야 한다는 것이 유일한 제한 사항입니다.

private fixed char name[30];

안전한 코드에서 배열을 포함하는 C# 구조체는 배열 요소를 포함하지 않습니다. 대신 구조체에 요소에 대한 참조가 포함되어 있습니다. unsafe 코드 블록에서 사용될 경우 구조체에 고정 크기 배열을 포함할 수 있습니다.

pathName이 참조이므로 다음 struct의 크기는 배열에 있는 요소의 수에 따라 달라지지 않습니다.

구조체는 안전하지 않은 코드에 포함된 배열을 포함할 수 있습니다. 다음 예제에서 fixedBuffer 배열은 고정 크기입니다. fixed 문을 사용하여 첫 번째 요소에 대한 포인터를 가져옵니다. 이 포인터를 통해 배열의 요소에 액세스합니다. fixed 문은 fixedBuffer의 인스턴스 필드를 메모리의 특정 위치에 고정합니다.

128개 요소 char 배열의 크기는 256바이트입니다. 고정 크기 문자 버퍼는 인코딩에 관계없이 항상 문자당 2바이트를 사용합니다.

또 다른 일반적인 고정 크기 배열은 bool 배열입니다. bool 배열의 요소 크기는 항상 1바이트입니다. bool 배열은 비트 배열이나 버퍼를 만드는 데 적합하지 않습니다.

이전 예제에서는 고정 크기 버퍼가 있는 unsafe struct방법을 보여줍니다.

internal unsafe struct Buffer
{
    public fixed char fixedBuffer[128];
}

Buffer에 대해 컴파일에서 생성된 C#은 다음과 같은 특성이 있습니다.

internal struct Buffer
{
    [StructLayout(LayoutKind.Sequential, Size = 256)]
    [CompilerGenerated]
    [UnsafeValueType]
    public struct <fixedBuffer>e__FixedBuffer
    {
        public char FixedElementField;
    }

    [FixedBuffer(typeof(char), 128)]
    public <fixedBuffer>e__FixedBuffer fixedBuffer;
}

고정 크기 버퍼는 다음과 같은 방법으로 일반 배열과 다릅니다.

  • unsafe 컨텍스트에서만 사용할 수 있습니다.
  • 단지 구조체의 인스턴스 필드일 수 있습니다.
  • 항상 벡터 또는 1차원 배열입니다.
  • 선언에는 fixed char id[8]와 같은 길이가 포함되어야 합니다. fixed char id[]를 사용할 수 없습니다.

포인터를 사용하여 바이트 배열을 복사하는 방법

다음 예제에서는 포인터를 사용하여 배열 간에 바이트를 복사합니다.

이 예제에서는 Copy 메서드에서 포인터를 사용할 수 있도록 하는 unsafe 키워드를 사용합니다. fixed 문은 소스 및 대상 배열에 대한 포인터를 선언하는 데 사용됩니다. fixed 문은 소스 및 대상 배열의 위치가 메모리에 고정되므로 가비지 수집에 의해 이동되지 않습니다. fixed 블록이 완료되면 배열에 대한 메모리 블록이 고정 해제됩니다. 이 예제의 Copy 메서드는 unsafe 키워드를 사용하므로 AllowUnsafeBlocks 컴파일러 옵션으로 컴파일해야 합니다.

이 예제에서는 두 번째 관리되지 않는 포인터보다는 인덱스를 사용하여 두 배열의 요소에 액세스합니다. pSourcepTarget 포인터의 선언은 배열을 고정합니다. 이 기능은 C# 7.3부터 사용할 수 있습니다.

함수 포인터

C#은 안전한 함수 포인터 개체를 정의하는 delegate 형식을 제공합니다. 대리자를 호출하려면 System.Delegate에서 파생된 형식을 인스턴스화하고 해당 Invoke 메서드에 대한 가상 메서드 호출을 수행해야 합니다. 이 가상 호출은 callvirt IL 명령을 사용합니다. 성능이 중요한 코드 경로에서는 calli IL 명령을 사용하는 것이 더 효율적입니다.

delegate* 구문을 사용하여 함수 포인터를 정의할 수 있습니다. 컴파일러는 delegate 개체를 인스턴스화하고 Invoke를 호출하는 대신 calli 명령을 사용하여 함수를 호출합니다. 다음 코드에서는 delegate 또는 delegate*를 사용하여 같은 형식의 두 개체를 결합하는 두 메서드를 선언합니다. 첫 번째 메서드는 System.Func<T1,T2,TResult> 대리자 형식을 사용합니다. 두 번째 메서드는 동일한 매개 변수 및 반환 형식이 포함된 delegate* 선언을 사용합니다.

다음 코드에서는 정적 로컬 함수를 선언하고 해당 로컬 함수에 대한 포인터를 사용하여 UnsafeCombine 메서드를 호출하는 방법을 보여 줍니다.

위의 코드는 함수 포인터로 액세스되는 함수에 대한 몇 가지 규칙을 보여 줍니다.

  • 함수 포인터는 unsafe 컨텍스트에서만 선언할 수 있습니다.
  • delegate*를 사용하거나 delegate*를 반환하는 메서드는 unsafe 컨텍스트에서만 호출할 수 있습니다.
  • 함수의 주소를 가져오는 & 연산자는 static 함수에서만 사용할 수 있습니다. 이 규칙은 멤버 함수와 로컬 함수에 모두 적용됩니다.

구문은 delegate 형식 선언 및 포인터 사용과 유사합니다. delegate* 접미사는 선언이 함수 포인터임을 나타냅니다. 메서드 그룹을 함수 포인터에 할당할 때 &는 연산에 메서드의 주소가 사용됨을 나타냅니다.

managedunmanaged 키워드를 사용하여 delegate*에 대한 호출 규칙을 지정할 수 있습니다. 또한 unmanaged 함수 포인터의 경우 호출 규칙을 지정할 수 있습니다. 다음 선언에서는 각각의 예제를 보여 줍니다. 첫 번째 선언은 managed 호출 규칙(기본값)을 사용합니다. 다음 4개에서는 unmanaged 호출 규칙을 사용합니다. 각각은 ECMA 335 호출 규칙(Cdecl, Stdcall, Fastcall 또는 Thiscall) 중 하나를 지정합니다. 마지막 선언은 플랫폼에 unmanaged 대한 기본 호출 규칙을 선택하도록 CLR에 지시하는 호출 규칙을 사용합니다. CLR은 런타임에 호출 규칙을 선택합니다.

포인터 배열

포인터로 1차원 배열 참조하기

변수 및 상수에 대한 참조를 할 수 있는 것처럼 배열에 대해서도 포인터 변수를 사용해서 참조해서 빠르게 사용할 수 있습니다. 우리가 지금까지 사용했던 배열의 이름은 그 자체가 배열의 시작 주소(첫 번째 요소의 주소)를 가지고 있는 포인터입니다.

그림: 포인터 배열

포인터 배열

코드: pointer_array.c

#include <stdio.h>

int main()
{
    int arr[3] = { 1, 3, 5 };

    int* ptr1 = arr; //[1] 배열 이름은 배열의 시작 주소
    int* ptr2 = &arr[0]; //[2] 0번째 배열의 시작 주소는 배열의 이름과 동일

    printf("%d, %d, %d\n", arr[0], arr[1], arr[2]); // 배열 직접 출력 
    printf("%d, %d, %d\n", *ptr1, *(ptr1 + 1), *(ptr1 + 2)); // 포인터로 배열 출력
    printf("%d, %d, %d\n", *(ptr2 + 0), *(ptr2 + 1), *(ptr2 + 2));

    return 0;
}
1, 3, 5
1, 3, 5
1, 3, 5 

배열의 이름인 arr과 첫 번째 배열 요소의 주솟값은 같은 주솟값을 반환해 줍니다. 그래서 [1]과 [2] 코드 모양은 같은 배열을 참조합니다. 그리고 포인터 배열을 역참조할 때에는 포인터 변수에 산술 연산자를 사용하여 + n 만큼 증가하면서 배열의 값을 참조할 수 있습니다. *ptr1과 *(ptr1 + 0)은 같은 내용이므로 원하는 모양을 사용하면 됩니다.

포인터 변수로 1차원 배열 참조하여 출력하기

다음 코드는 포인터 변수로 1차원 배열을 참조해서 사용하는 예제입니다.

코드: pointer_one_array.c

// pointer_one_array.c
#include <stdio.h>

int main(void)
{
    int a[5] = { 10, 20, 30, 40, 50 };
    int* p;

    // 포인터로 배열 참조 
    p = a; // 배열명 자체가 배열의 시작 주소이므로 & 기호 생략 

    // 배열 직접 출력
    printf("%d %d %d %d %d\n", a[0], a[1], a[2], a[3], a[4]);

    // 포인터 변수 증감으로 배열 참조 
    printf("%d %d %d %d %d\n", *p, *(p + 1), *(p + 2), *(p + 3), *(p + 4));

    return 0;
}
10 20 30 40 50
10 20 30 40 50

08 포인터 배열로 배열의 합 구하기

1차원 배열에 대한 참조를 포인터 변수에 담아 놓은 후에 이 포인터 변수를 사용해서 배열의 데이터 중에서 짝수의 합을 구하는 예제를 만들어 보겠습니다. 다음 코드를 작성 후 실행해보세요.

pointer_array_sum.c, PointerArraySum.c, 포인터배열_배수의합.c

// 포인터배열_배수의합.c
#include <stdio.h>

int main(void)
{
    int data[] = { 3, 2, 1, 4, 5 };
    int sum = 0;
    int* p;
    int i;

    p = data; // 배열의 시작 주소를 포인터에 기록

    // 데이터를 반복 : 포인터 변수를 통해서 반복
    for (i = 0; i < sizeof(data) / sizeof(int); i++)
    {
        if (*(p + i) % 2 == 0)
        {
            sum += *(p + i);
        }
    }

    printf("짝수의 합: %d\n", sum);
}
짝수의 합: 6

포인터로 2차원 배열 참조하기

2차원 배열도 포인터 변수로 참조한 후 포인터 증감을 사용해서 값을 얻어낼 수 있습니다. 사실, 2차원 배열은 사람이 생각하기 편한 구조로 표현이 되지만, 실제 메모리 상에서는 순서대로 값이 저장되어 있어 포인터로 참조 후 전체 데이터를 포인터 증감을 통해 얻어낼 수 있는 것입니다.

코드: PointerArrayMulti.c

#include <stdio.h>

int main(void)
{
    int arr[2][2] = { 2, 4, 6, 8 };

    int* ptr = arr; // 2차원 배열 참조

    for (int i = 0; i < 4; i++)
    {
        printf("%d\t", *(ptr + i));
    }

    return 0;
}
2       4       6       8

09 포인터 배열로 이차원 배열 참조하기

pointer_array_multi.c, 포인터배열_이차원배열참조.c

// 포인터배열_이차원배열참조.c
/*
    포인터변수에 2차원배열을 참조해서 호출
*/
#include <stdio.h>

int main(void)
{
    int arr[2][3] = { {1, 2, 3}, {4, 5, 6} };
    int* p;
    int i;

    p = arr;

    for (i = 0; i < sizeof(arr) / sizeof(int); i++)
    {
        printf("%d \n", *(p + i));
    }
}
1
2
3
4
5
6

포인터 배열

포인터로 1차원 배열 참조하기

변수 및 상수에 대한 참조를 할 수 있는 것처럼 배열에 대해서도 포인터 변수를 사용해서 참조해서 빠르게 사용할 수 있습니다. 우리가 지금까지 사용했던 배열의 이름은 그 자체가 배열의 시작 주소(첫 번째 요소의 주소)를 가지고 있는 포인터입니다.

그림: 포인터 배열

포인터 배열

코드: pointer_array.c

#include <stdio.h>

int main()
{
    int arr[3] = { 1, 3, 5 };

    int* ptr1 = arr; //[1] 배열 이름은 배열의 시작 주소
    int* ptr2 = &arr[0]; //[2] 0번째 배열의 시작 주소는 배열의 이름과 동일

    printf("%d, %d, %d\n", arr[0], arr[1], arr[2]); // 배열 직접 출력 
    printf("%d, %d, %d\n", *ptr1, *(ptr1 + 1), *(ptr1 + 2)); // 포인터로 배열 출력
    printf("%d, %d, %d\n", *(ptr2 + 0), *(ptr2 + 1), *(ptr2 + 2));

    return 0;
}
1, 3, 5
1, 3, 5
1, 3, 5 

배열의 이름인 arr과 첫 번째 배열 요소의 주솟값은 같은 주솟값을 반환해 줍니다. 그래서 [1]과 [2] 코드 모양은 같은 배열을 참조합니다. 그리고 포인터 배열을 역참조할 때에는 포인터 변수에 산술 연산자를 사용하여 + n 만큼 증가하면서 배열의 값을 참조할 수 있습니다. *ptr1과 *(ptr1 + 0)은 같은 내용이므로 원하는 모양을 사용하면 됩니다.

포인터 변수로 1차원 배열 참조하여 출력하기

다음 코드는 포인터 변수로 1차원 배열을 참조해서 사용하는 예제입니다.

코드: pointer_one_array.c

// pointer_one_array.c
#include <stdio.h>

int main(void)
{
    int a[5] = { 10, 20, 30, 40, 50 };
    int* p;

    // 포인터로 배열 참조 
    p = a; // 배열명 자체가 배열의 시작 주소이므로 & 기호 생략 

    // 배열 직접 출력
    printf("%d %d %d %d %d\n", a[0], a[1], a[2], a[3], a[4]);

    // 포인터 변수 증감으로 배열 참조 
    printf("%d %d %d %d %d\n", *p, *(p + 1), *(p + 2), *(p + 3), *(p + 4));

    return 0;
}
10 20 30 40 50
10 20 30 40 50

08 포인터 배열로 배열의 합 구하기

1차원 배열에 대한 참조를 포인터 변수에 담아 놓은 후에 이 포인터 변수를 사용해서 배열의 데이터 중에서 짝수의 합을 구하는 예제를 만들어 보겠습니다. 다음 코드를 작성 후 실행해보세요.

pointer_array_sum.c, PointerArraySum.c, 포인터배열_배수의합.c

// 포인터배열_배수의합.c
#include <stdio.h>

int main(void)
{
    int data[] = { 3, 2, 1, 4, 5 };
    int sum = 0;
    int* p;
    int i;

    p = data; // 배열의 시작 주소를 포인터에 기록

    // 데이터를 반복 : 포인터 변수를 통해서 반복
    for (i = 0; i < sizeof(data) / sizeof(int); i++)
    {
        if (*(p + i) % 2 == 0)
        {
            sum += *(p + i);
        }
    }

    printf("짝수의 합: %d\n", sum);
}
짝수의 합: 6

포인터로 2차원 배열 참조하기

2차원 배열도 포인터 변수로 참조한 후 포인터 증감을 사용해서 값을 얻어낼 수 있습니다. 사실, 2차원 배열은 사람이 생각하기 편한 구조로 표현이 되지만, 실제 메모리 상에서는 순서대로 값이 저장되어 있어 포인터로 참조 후 전체 데이터를 포인터 증감을 통해 얻어낼 수 있는 것입니다.

코드: PointerArrayMulti.c

#include <stdio.h>

int main(void)
{
    int arr[2][2] = { 2, 4, 6, 8 };

    int* ptr = arr; // 2차원 배열 참조

    for (int i = 0; i < 4; i++)
    {
        printf("%d\t", *(ptr + i));
    }

    return 0;
}
2       4       6       8

09 포인터 배열로 이차원 배열 참조하기

pointer_array_multi.c, 포인터배열_이차원배열참조.c

// 포인터배열_이차원배열참조.c
/*
    포인터변수에 2차원배열을 참조해서 호출
*/
#include <stdio.h>

int main(void)
{
    int arr[2][3] = { {1, 2, 3}, {4, 5, 6} };
    int* p;
    int i;

    p = arr;

    for (i = 0; i < sizeof(arr) / sizeof(int); i++)
    {
        printf("%d \n", *(p + i));
    }
}
1
2
3
4
5
6

C 언어 문자열과 포인터 사용 예제

이 예제는 C 언어에서 문자열과 포인터를 사용하는 간단한 예제입니다. 문자열을 배열에 저장하거나 포인터를 통해 문자열을 초기화하는 방법, 배열과 포인터 배열의 차이, 그리고 printf() 함수를 사용하여 문자열을 출력하는 방법을 보여줍니다.

코드: pointerarray.c

#include <stdio.h>

int main(void)
{
    char a[] = "안녕\0하세요.";
    char* b;
    char* c[2];

    b = "반갑습니다.";
    c[0] = "또봐~";
    c[1] = "낼봐~";

    printf("%s\n", a);
    printf("%s\n", b);
    printf("%s %s\n", c[0], c[1]);

    return 0;
}
안녕
반갑습니다.
또봐~ 낼봐~

이 예제는 main() 함수에서 세 개의 변수를 선언하고 초기화합니다. 첫 번째 변수인 a는 문자열 배열로, "안녕""하세요." 사이에 널 문자(\0)가 들어간 "안녕하세요." 문자열을 저장합니다. 두 번째 변수인 b는 문자열 포인터로, "반갑습니다." 문자열을 가리키도록 초기화합니다. 세 번째 변수인 c는 문자열 포인터 배열로, "또봐~""낼봐~" 문자열을 각각 가리키도록 초기화합니다.

그리고 나서 printf() 함수를 사용하여 a, b, c 변수에 저장된 문자열을 출력합니다. %s는 문자열 포맷 지정자로, 문자열을 출력할 때 사용됩니다. 마지막으로 main() 함수는 0을 반환하여 프로그램이 성공적으로 종료됨을 알립니다.

포인터 변수에 증감 연산자 사용하기

C 언어에서 포인터 변수는 변수의 주소를 저장하고 해당 변수를 간접적으로 참조할 수 있게 해줍니다. 이번에는 포인터 변수의 연산 중 하나인 증감 연산자에 대해 알아보겠습니다.

/*
    포인터 변수의 연산 : 증감 연산자
*/
#include <stdio.h>

int main(void)
{
    int num[] = { 1, 2, 3, 4, 5 };
    int* pnum = num; // pnum은 num의 첫 번째 원소를 가리킨다.

    //*pnum++ : *(pnum++)
    printf("*pnum++ : %d \n", *pnum++); // 1, pnum이 가리키는 원소 출력, pnum을 1 증가

    //*pnum-- : *(pnum--)
    printf("*pnum-- : %d \n", *pnum--); // 2, pnum이 가리키는 원소를 출력하고, pnum을 1 감소시킨다.

    //*++pnum : *(++pnum)
    printf("*++pnum : %d \n", *++pnum); // 2, pnum을 1 증가시키고, pnum이 가리키는 원소를 출력한다.

    //*--pnum : *(--pnum)
    printf("*--pnum : %d \n", *--pnum); // 1, pnum을 1 감소시키고, pnum이 가리키는 원소를 출력한다.

    return 0;
}

코드를 살펴보면 int 형 배열 num이 선언되고, 첫 번째 원소의 주소를 가리키는 포인터 변수 pnum이 선언됩니다.

int num[] = {1, 2, 3, 4, 5};
int *pnum = num;

이제 증감 연산자를 이용하여 포인터 변수를 조작하고 배열의 원소에 접근할 수 있습니다.

printf("*pnum++ : %d \n", *pnum++); // 1, pnum이 가리키는 원소를 출력하고, pnum을 1 증가시킨다.
printf("*pnum-- : %d \n", *pnum--); // 2, pnum이 가리키는 원소를 출력하고, pnum을 1 감소시킨다.
printf("*++pnum : %d \n", *++pnum); // 2, pnum을 1 증가시키고, pnum이 가리키는 원소를 출력한다.
printf("*--pnum : %d \n", *--pnum); // 1, pnum을 1 감소시키고, pnum이 가리키는 원소를 출력한다.

위 코드에서 *pnum++ 연산은 pnum이 가리키는 원소를 출력한 후 pnum을 1 증가시키는 연산입니다. *pnum-- 연산은 pnum이 가리키는 원소를 출력한 후 pnum을 1 감소시키는 연산입니다. *++pnum 연산은 pnum을 1 증가시킨 후 pnum이 가리키는 원소를 출력하는 연산입니다. *--pnum 연산은 pnum을 1 감소시킨 후 pnum이 가리키는 원소를 출력하는 연산입니다.

즉, 이 예제 코드에서는 포인터 변수를 이용하여 배열의 원소에 접근하고, 포인터 변수를 증감 연산자를 이용하여 값을 변경하는 것을 보여주고 있습니다.

증감 연산자는 포인터 연산에서 매우 유용합니다. 하지만 이러한 연산이 포인터 변수의 값이 잘못된 메모리 영역을 가리키도록 만들 수도 있으므로, 사용할 때 주의해야 합니다.

포인터 함수 사용 이해하기

포인터는 특정 데이터의 메모리 주소를 저장하는 변수로, 이를 통해 메모리에 직접 접근하거나 함수 호출을 통해 변수의 값을 변경하는 등의 다양한 작업을 수행할 수 있습니다. 이번 예제에서는 포인터를 이용하여 함수 내에서 변수의 값을 변경하는 방법에 대해 알아보겠습니다.

코드: pointer_function.c

#include <stdio.h>

// 'update_value' 함수는 정수형 포인터를 매개변수로 받아 해당 주소의 값을 변경합니다.
void update_value(int* value)
{
    *value += 5; // 전달받은 포인터가 가리키는 주소에 저장된 값을 5 증가시킵니다.
}

int main(void)
{
    int number = 20; // 'number' 변수를 선언하고 초기 값으로 20을 할당합니다.

    printf("update_value 함수 호출 전: %d\n", number); // 'number'의 초기 값 출력

    update_value(&number); // 'update_value' 함수를 호출하면서 'number'의 주소를 인자로 전달합니다.
    // 이렇게 하면 함수 내에서 직접 'number'의 값을 변경할 수 있습니다.

    printf("update_value 함수 호출 후: %d\n", number); // 변경된 'number' 값 출력

    return 0; 
}

이 코드를 실행하면 다음과 같은 결과를 볼 수 있습니다:

update_value 함수 호출 전: 20
update_value 함수 호출 후: 25

이 예제에서는 'update_value'라는 함수를 선언하였습니다. 이 함수는 정수형 포인터를 매개변수로 받아 해당 포인터가 가리키는 메모리 주소에 저장된 값을 5 증가시킵니다. 'main' 함수에서는 'number'라는 변수를 선언하고 초기값으로 20을 할당한 후, 이 변수의 주소를 'update_value' 함수에 전달합니다. 이를 통해 'update_value' 함수 내에서 'number'의 값을 직접 변경할 수 있습니다.

함수 매개변수에 포인터를 사용하여 변수의 값을 변경하기

이 예제는 C 언어에서 함수를 사용하여 변수의 값을 변경하는 방법을 보여줍니다.

코드: pointer_function_parameter.c

#include <stdio.h>

// 매개변수로 전달된 숫자를 50으로 변경하는 함수
void set_to_fifty(int* number)
{
    *number = 50;
}

// 매개변수로 전달된 숫자를 100으로 변경하는 함수
void set_to_hundred(int* number)
{
    *number = 100;
}

int main(void)
{
    int x = 5;

    printf("함수를 호출하기 전 x 값: %d\n", x);

    set_to_fifty(&x);
    printf("set_to_fifty() 함수를 호출한 후의 x값: %d \n", x);

    set_to_hundred(&x);
    printf("set_to_hundred() 함수를 호출한 후의 x값: %d \n", x);

    return 0;
}
함수를 호출하기 전 x 값: 5
set_to_fifty() 함수를 호출한 후의 x값: 50 
set_to_hundred() 함수를 호출한 후의 x값: 100 

해당 코드는 C 언어에서 함수를 사용하여 변수의 값을 변경하는 방법을 보여줍니다. 먼저, set_to_fifty() 함수는 매개변수로 전달된 숫자를 50으로 변경하고, set_to_hundred() 함수는 매개변수로 전달된 숫자를 100으로 변경합니다.

main() 함수에서는 먼저 변수 x를 5로 초기화한 후, set_to_fifty() 함수를 호출하여 x의 값을 50으로 변경합니다. 그리고 set_to_hundred() 함수를 호출하여 x의 값을 100으로 변경합니다.

코드를 실행하면, set_to_fifty() 함수를 호출한 후 x의 값이 50으로 변경되고, set_to_hundred() 함수를 호출한 후 x의 값이 100으로 변경됨을 확인할 수 있습니다. 이렇게 함수를 사용하여 변수의 값을 변경할 수 있습니다.

함수 포인터

함수 포인터는 함수의 시작 주소를 포함하는 특정 유형의 포인터입니다.

반환값 (*함수포인터이름)(매개 변수 타입, ...);

코드: function_pointer.c

#include <stdio.h>

// 원본 함수 선언
int sum(int f, int s)
{
    return (f + s); 
}

int main(void)
{
    // 2개의 int 타입 매개 변수를 갖는 
    // 함수를 대신해서 호출해주는 func_sum 이름의 함수 포인터 생성 
    int (*func_sum)(int, int); 

    // 함수 포인터에 함수의 시작 주소 할당 
    func_sum = &sum; 

    // 함수 포인터를 사용하여 함수 호출 
    printf("%d\n", func_sum(3, 5)); // 8

    return 0;
}

결과

8

포인터를 이용한 다중 값 반환

C 언어는 포인터를 이용해 함수에서 다중 값을 반환하는 방법을 제공합니다. 이번에는 이러한 방법을 사용하는 예제 코드를 살펴보고, 코드를 이해하고 활용하는 방법에 대해 알아보겠습니다.

포인터를 이용한 다중 값 반환

C 언어에서 함수는 일반적으로 하나의 값만을 반환합니다. 하지만 함수에서 두 개 이상의 값을 반환해야 하는 경우가 있습니다. 이때 구조체나 배열 등을 이용해 값을 반환할 수 있습니다. 하지만 포인터를 이용해 값을 반환하는 방법도 있습니다.

이번에 살펴볼 코드는 calculate() 함수에서 두 개의 정수값을 받아 덧셈, 뺄셈, 곱셈, 나눗셈을 계산한 후, 결과 값을 배열에 저장합니다. 이 배열은 함수 호출한 곳으로 전달됩니다. 이를 구현한 코드는 다음과 같습니다.

// pointer_function_multi_return.c
#include <stdio.h>

// calculate 함수 정의
// result: 결과 값을 저장할 배열
// f: 덧셈, 뺄셈, 곱셈, 나눗셈을 할 첫 번째 정수
// s: 덧셈, 뺄셈, 곱셈, 나눗셈을 할 두 번째 정수
void calculate(int* result, int f, int s)
{
    result[0] = f + s; // 덧셈 결과 저장
    result[1] = f - s; // 뺄셈 결과 저장
    result[2] = f * s; // 곱셈 결과 저장
    result[3] = f / s; // 나눗셈 결과 저장
}

// main 함수 정의
int main(void)
{
    int result[4]; // 결과 값을 저장할 배열
    int first_number = 10; // 첫 번째 정수
    int second_number = 20; // 두 번째 정수
    calculate(result, first_number, second_number); // calculate 함수 호출

    // 각 연산 결과 출력
    printf("first_number + second_number = %d\n", result[0]);
    printf("first_number - second_number = %d\n", result[1]);
    printf("first_number * second_number = %d\n", result[2]);
    printf("first_number / second_number = %d\n", result[3]);

    return 0;
}

calculate() 함수는 세 개의 인자를 받습니다. 첫 번째 인자는 포인터로 선언된 정수형 배열인 result입니다. 이 배열은 함수에서 연산한 값을 저장하기 위해 사용됩니다. 두 번째와 세 번째 인자는 각각 덧셈, 뺄셈, 곱셈, 나눗셈을 계산할 두 정수값입니다.

calculate() 함수에서는 받은 두 정수값을 이용하여 덧셈, 뺄셈, 곱셈, 나눗셈을 계산하고, result 배열의 각 인덱스에 해당하는 위치에 값을 저장합니다.

메인 함수에서는 calculate() 함수를 호출하고, result 배열에 저장된 값을 출력합니다. 결과는 다음과 같습니다.

first_number + second_number = 30
first_number - second_number = -10
first_number * second_number = 200
first_number / second_number = 0

포인터를 이용한 다중 값 반환의 장점

포인터를 이용해 함수에서 다중 값을 반환하는 방법은 구조체나 배열 등의 복잡한 데이터 타입을 사용하지 않고도 간단하게 값을 반환할 수 있습니다. 또한 포인터를 이용하여 값을 전달하기 때문에, 함수 내부에서 직접 값을 반환하는 것보다 더욱 효율적인 메모리 관리를 할 수 있습니다.

예를 들어, 구조체를 이용해 값을 반환하는 경우, 구조체를 선언하고 초기화해야 하며, 반환할 값이 많아질수록 구조체의 크기도 커지게 됩니다. 이에 반해 포인터를 이용해 값을 반환하는 경우, 반환할 값의 개수에 상관 없이 포인터의 크기는 일정합니다.

포인터를 이용한 다중 값 반환의 주의점

하지만 포인터를 다루는 것이 필요하기 때문에, 초보자들은 어려울 수 있습니다. 포인터는 메모리 주소를 가리키기 때문에, 포인터 변수를 선언하고 초기화하는 것이 중요합니다. 또한 포인터 변수의 사용은 주의해서 해야 합니다. 잘못된 포인터 사용은 프로그램 충돌과 같은 치명적인 오류를 발생시킬 수 있습니다.

따라서 포인터를 이용한 다중 값 반환을 사용할 때는 적절한 설명과 예시를 통해 학습해야 합니다. 또한 포인터 사용 시 주의해야 할 사항을 숙지하고 안전한 방법으로 코드를 작성해야 합니다.

결론

이번에는 C 언어에서 포인터를 이용해 함수에서 다중 값을 반환하는 방법에 대해 살펴보았습니다. 포인터를 이용하면 간단하게 값을 반환할 수 있으며, 메모리 관리에 대한 효율성도 높아집니다. 하지만 포인터를 다루는 것이 어려울 수 있으므로, 적절한 설명과 예시를 통해 학습하고, 포인터 사용 시 주의해야 할 사항을 숙지해야 합니다.

포인터 확장

이 코드는 C 언어에서 포인터를 사용하는 방법에 대한 예제입니다.

코드: 포인터확장.c

#include <stdio.h>

int main(void) {
    int i = 0, j = 0;
    char* name = "홍길동"; // 포인터 변수를 선언하고 문자열을 저장
    char* names[] = { "안녕하세요", "반가워", "또만나요" }; // 포인터의 배열을 선언하고 초기화
    char(*addr)[5]; // 배열의 포인터를 선언
    char ad[3][5] = { "abcd", "efgh", "ijkl" }; // 2차원 배열을 선언하고 초기화
    char** nickname = &name; // 포인터의 포인터를 선언하고 초기화
    addr = ad; // 배열의 포인터에 2차원 배열을 대입

    printf("[0] %s\n", name); // 문자열을 출력
    for (i = 0; i < 3; i++) {
        printf("[1] %s\t", names[i]);
    }
    printf("\n");
    for (i = 0; i < 3; i++) {
        for (j = 0; j < 5; j++) {
            printf("%c", addr[i][j]); // 2차원 배열을 참조하는 배열의 포인터를 이용하여 출력
        }
        printf("\n");
    }
    printf("\n");
    printf("[3] %s\n", *nickname); // 포인터의 포인터를 이용하여 문자열을 출력

    return 0;
}
[0] 홍길동
[1] 안녕하세요  [1] 반가워      [1] 또만나요
abcd
efgh
ijkl

[3] 홍길동

다음은 위 코드에서 포인터와 관련된 핵심적인 내용입니다.

포인터 변수

char* name = "홍길동";

name은 문자열을 가리키는 포인터 변수입니다. 이 변수는 문자열 "홍길동"의 주소를 가리킵니다. 포인터 변수는 * 연산자를 사용하여 선언됩니다. char*은 문자열을 가리키는 포인터의 타입을 나타냅니다.

포인터의 배열

char* names[] = { "안녕하세요", "반가워", "또만나요" };

names는 문자열을 가리키는 포인터의 배열입니다. 배열의 각 요소는 문자열을 가리키는 포인터입니다. char*로 선언되었으므로, names의 각 요소는 char* 타입입니다. names의 요소에 접근하기 위해서는 인덱스([]) 연산자를 사용합니다.

배열의 포인터

char(*addr)[5];

addr5개의 문자열을 저장할 수 있는 배열을 가리키는 포인터입니다. char(*)[5]는 5개의 문자열을 저장하는 배열의 포인터 타입을 나타냅니다. 배열의 포인터를 사용하여 2차원 배열을 참조할 수 있습니다.

포인터의 포인터

char** nickname = &name;

nickname은 포인터 변수 name의 주소를 저장하는 포인터입니다. char**는 포인터 변수를 가리키는 포인터의 타입을 나타냅니다. 포인터의 포인터를 사용하여 포인터 변수의 값을 변경할 수 있습니다.

포인터는 C 언어에서 매우 중요한 개념 중 하나입니다. 포인터를 이해하면 메모리를 보다 효과적으로 관리할 수 있으며, C 언어에서 더욱 다양한 작업을 수행할 수 있습니다.

면접 및 코딩 테스트 준비

  1. 포인터란 무엇인가요?
  2. 포인터 변수는 어떻게 선언하나요?
  3. 포인터 변수의 값은 무엇인가요?
  4. 포인터 변수를 초기화하지 않으면 어떤 값을 가지나요?
  5. 포인터 변수를 사용할 때 주의할 점은 무엇인가요?
  6. 포인터 변수는 어떤 연산을 수행할 수 있나요?
  7. 포인터 변수는 배열과 어떤 차이가 있나요?
  8. 포인터 변수를 사용하여 문자열을 처리할 때 어떤 함수를 사용하나요?
  9. 포인터 변수를 사용하여 동적 메모리 할당을 할 때 어떤 함수를 사용하나요?
  10. 포인터 변수를 사용하여 구조체를 처리할 때 어떻게 접근하나요?
  11. 더블 포인터란 무엇인가요?
  12. 더블 포인터는 어떻게 선언하나요?
  13. 더블 포인터 변수의 값은 무엇인가요?
  14. 더블 포인터 변수를 초기화하지 않으면 어떤 값을 가지나요?
  15. 더블 포인터 변수를 사용할 때 주의할 점은 무엇인가요?
  16. 더블 포인터 변수는 어떤 연산을 수행할 수 있나요?
  17. 더블 포인터 변수는 포인터 변수와 어떤 차이가 있나요?
  18. 함수에서 포인터를 사용할 때는 어떤 방식으로 선언하나요?
  19. 함수에서 포인터를 사용하여 값을 반환할 때는 어떤 방식으로 처리하나요?
  20. 포인터 변수를 사용하여 배열을 처리할 때는 어떻게 접근하나요?
  21. const 키워드는 무엇인가요?
  22. const 포인터는 어떻게 선언하나요?
  23. const 포인터와 일반 포인터는 어떤 차이가 있나요?
  24. void 포인터는 무엇인가요?
  25. void 포인터를 사용할 때 주의할 점은 무엇인가요?
  26. typedef를 사용하여 포인터를 선언하는 방법은 무엇인가요?
  27. 함수 포인터는 무엇인가요?
  28. 함수 포인터를 선언하는 방법은 무엇인가요?
  29. 함수 포인터를 사용하여 함수를 호출하는 방법은 무엇인가요?
  30. 함수 포인터를 사용하여 콜백 함수를 호출하는 방법은 무엇인가요?
  31. 구조체 포인터란 무엇인가요?
  32. 구조체 포인터를 선언하는 방법은 무엇인가요?
  33. 구조체 포인터를 사용하여 구조체 멤버에 접근하는 방법은 무엇인가요?
  34. 구조체 포인터를 사용하여 구조체 배열에 접근하는 방법은 무엇인가요?
  35. 구조체 포인터를 사용하여 동적 구조체를 생성하는 방법은 무엇인가요?
  36. 구조체 포인터를 사용하여 구조체를 함수의 인자로 전달하는 방법은 무엇인가요?
  37. 포인터와 배열을 함께 사용하는 방법은 무엇인가요?
  38. 포인터와 함수를 함께 사용하는 방법은 무엇인가요?
  39. 포인터와 문자열을 함께 사용하는 방법은 무엇인가요?
  40. 포인터와 동적 메모리 할당을 함께 사용하는 방법은 무엇인가요?
  41. 포인터와 파일 입출력을 함께 사용하는 방법은 무엇인가요?
  42. 함수에서 포인터를 사용하여 배열을 처리하는 방법은 무엇인가요?
  43. 함수에서 포인터를 사용하여 구조체를 처리하는 방법은 무엇인가요?
  44. 함수에서 포인터를 사용하여 문자열을 처리하는 방법은 무엇인가요?
  45. 함수에서 포인터를 사용하여 동적 메모리를 처리하는 방법은 무엇인가요?
  46. 함수에서 포인터를 사용하여 파일 입출력을 처리하는 방법은 무엇인가요?
  47. 포인터와 다중 포인터를 함께 사용하는 방법은 무엇인가요?
  48. 포인터를 사용하여 연결 리스트를 구현하는 방법은 무엇인가요?
  49. 포인터를 사용하여 이중 연결 리스트를 구현하는 방법은 무엇인가요?
  50. 포인터를 사용하여 트리를 구현하는 방법은 무엇인가요?

문제 풀이

정보처리기능사 실기 문제: 포인터와 문자열을 이용한 출력

이 문제에서는 C 언어를 사용하여 문자열 포인터를 다양한 방식으로 활용하여 문자열 및 문자를 출력하는 프로그램을 작성하는 것이 목표입니다. 이 과정에서 포인터 연산과 문자열의 처리 방법에 대해 이해해야 합니다.

문제: 문자열 포인터 p가 주어졌을 때, 다양한 포인터 연산을 이용해 특정 문자 및 문자열을 출력하는 C 프로그램을 작성하시오.

#include <stdio.h>

main() {
    char *p = "KOREA";
    // 전체 문자열 출력
    printf("%s\n", p);
    // 포인터를 이용해 문자열의 일부 출력
    printf("%s\n", p + 3);
    // 포인터를 이용해 특정 문자 출력
    printf("%c\n", *p);
    printf("%c\n", *(p + 3));
    // 포인터를 이용해 특정 문자에 연산 적용 후 출력
    printf("%c\n", *p + 2);
}

해결 방법:

  1. #include <stdio.h>를 사용하여 표준 입출력 라이브러리를 포함합니다.
  2. main 함수를 정의하여 프로그램의 시작점을 만듭니다.
  3. 문자열 리터럴 "KOREA"를 가리키는 문자 포인터 p를 선언하고 초기화합니다.
  4. printf 함수를 사용하여 다음을 출력합니다:
    • 변수 p가 가리키는 전체 문자열 "KOREA".
    • 변수 p에 3을 더한 위치에서 시작하는 문자열 "EA", 즉 p 포인터의 4번째 문자부터 끝까지의 문자열.
    • p 포인터가 가리키는 첫 번째 문자 'K'.
    • p 포인터에 3을 더해 가리키는 위치의 문자 'E'.
    • p 포인터가 가리키는 문자에 2를 더한 결과, 즉 'K'의 ASCII 값에 2를 더한 문자.

이 문제는 포인터와 문자열을 함께 사용하는 방법, 특히 포인터 연산을 이용해 문자열의 특정 부분에 접근하고, 문자에 대한 연산을 수행하는 방법을 이해하는 데 중점을 둡니다.

정보처리기능사 실기 문제: 배열 요소의 합 계산하기

이 문제에서는 C 언어를 사용하여 배열에 값을 할당하고, 이 배열의 모든 요소의 합을 계산하여 출력하는 프로그램을 작성하는 것이 목표입니다. 배열 요소에 접근하는 두 가지 방법([] 연산자와 포인터 연산)을 사용하여 배열의 값을 초기화하고, 반복문을 통해 배열의 요소를 순회하여 합을 구하는 방법에 대해 이해해야 합니다.

문제: 정수형 배열 ary의 세 요소에 값을 할당한 후, 이 배열의 모든 요소의 합을 계산하여 출력하는 C 프로그램을 작성하시오.

#include <stdio.h>

int main() {
    int ary[3];
    int s = 0;
    // 배열 요소에 값 할당
    *(ary + 0) = 1;  // 첫 번째 요소에 1 할당
    ary[1] = *(ary + 0) + 2;  // 두 번째 요소에 첫 번째 요소의 값 + 2 할당
    ary[2] = *ary + 3;  // 세 번째 요소에 첫 번째 요소의 값 + 3 할당
    // 배열의 모든 요소의 합 계산
    for (int i = 0; i < 3; i++)
        s = s + ary[i];
    // 합 출력
    printf("%d", s);
}

해결 방법:

  1. #include <stdio.h>를 사용하여 표준 입출력 라이브러리를 포함합니다.
  2. main 함수를 정의하여 프로그램의 시작점을 만듭니다.
  3. 정수형 배열 ary[3]와 합을 저장할 변수 s를 선언합니다. s는 0으로 초기화합니다.
  4. 배열의 첫 번째 요소에 1을 할당합니다. 이때, 포인터 연산을 사용합니다.
  5. 배열의 두 번째 요소에 첫 번째 요소의 값에 2를 더한 값을 할당합니다.
  6. 배열의 세 번째 요소에 첫 번째 요소의 값에 3을 더한 값을 할당합니다.
  7. for 반복문을 사용하여 배열의 모든 요소를 순회하며, 각 요소의 값을 s에 더하여 합을 계산합니다.
  8. 최종적으로 계산된 합 sprintf 함수를 사용하여 출력합니다.

이 문제는 배열 요소에 값 할당하는 방법과 배열 요소를 순회하여 합을 계산하는 기본적인 프로그래밍 기술을 평가합니다. 배열과 포인터 연산을 이해하고 적절히 사용할 수 있는 능력이 중점적으로 다뤄집니다.

정보처리기능사 실기 문제: 포인터 배열을 활용한 수의 합 계산

이 문제에서는 C 언어를 사용하여 포인터 배열을 활용해 주어진 변수들의 합을 계산하고 그 결과를 출력하는 프로그램을 작성하는 것이 목표입니다. 이 과정에서 포인터의 기본적인 사용법과 배열의 포인터 활용 방법에 대해 이해해야 합니다.

문제: 정수형 변수 a, b, c에 각각 12, 24, 36이 할당되어 있습니다. 이 변수들의 주소를 포인터 배열 array에 저장한 후, array[1]의 값과 array[0]이 가리키는 값에 1을 더한 합을 계산하여 출력하는 C 프로그램을 작성하시오.

#include <stdio.h>

int main() {
    int* array[3];  // 포인터 배열 선언
    int a = 12, b = 24, c = 36;
    // 각 변수의 주소를 포인터 배열에 저장
    array[0] = &a;
    array[1] = &b;
    array[2] = &c;
    // 계산 및 출력
    printf("%d", *array[1] + **array + 1);
}

해결 방법:

  1. #include <stdio.h>를 사용하여 표준 입출력 라이브러리를 포함합니다.
  2. main 함수를 정의하여 프로그램의 시작점을 만듭니다.
  3. 정수형 포인터를 저장할 수 있는 배열 array[3]를 선언합니다.
  4. 정수형 변수 a, b, c를 선언하고 각각 12, 24, 36의 값을 할당합니다.
  5. array[0], array[1], array[2]에 각각 a, b, c의 주소를 저장합니다.
  6. *array[1]b의 값인 24를 참조하고, **arrayarray[0] 즉, a의 값을 참조하여 12를 가져오며, 이들의 합에 1을 더한 결과인 37을 계산합니다.
  7. printf 함수를 사용하여 최종 계산 결과를 출력합니다.

이 문제는 포인터와 배열의 포인터를 활용하여 변수의 주소를 저장하고 이를 통해 값을 참조하는 방법을 이해하는 데 중점을 둡니다. 포인터 배열의 사용법과 간접 참조를 통한 값의 계산 방법을 숙지하는 것이 중요합니다.

정보처리기능사 실기 문제: 배열과 포인터를 활용한 수열 계산

이 문제에서는 C 언어를 사용하여 배열과 포인터를 활용해 특정 수열의 값을 계산하고 그 결과의 합을 출력하는 프로그램을 작성하는 것이 목표입니다. 이 과정에서 배열의 인덱스를 조작하고 포인터를 통해 배열의 요소에 접근하는 방법에 대해 이해해야 합니다.

문제: 정수 배열 a{0, 2, 4, 8}로 초기화되어 있습니다. 배열 b의 각 요소를 배열 a의 연속된 요소들의 차(a[i] - a[i-1])로 설정한 후, 배열 ab의 해당 요소들을 더하여 총합 sum을 계산하고 출력하는 C 프로그램을 작성하시오.

#include <stdio.h>

int main() {
    int a[4] = {0, 2, 4, 8};
    int b[3];
    int* p;
    int sum = 0;
    // 배열 a의 연속된 요소들의 차를 배열 b에 설정하고 합 계산
    for (int i = 1; i < 4; i++) {
        p = a + i;
        b[i - 1] = *p - a[i - 1];
        sum = sum + b[i - 1] + a[i];
    }
    // 총합 출력
    printf("%d", sum);
}

해결 방법:

  1. #include <stdio.h>를 사용하여 표준 입출력 라이브러리를 포함합니다.
  2. main 함수를 정의하여 프로그램의 시작점을 만듭니다.
  3. 정수형 배열 a{0, 2, 4, 8}로 초기화하고, 빈 배열 b를 선언합니다.
  4. 포인터 p와 총합을 저장할 변수 sum을 선언합니다. sum은 0으로 초기화합니다.
  5. for 반복문을 사용하여 배열 a의 인덱스 1부터 3까지 반복합니다. 배열 b의 각 요소는 배열 a의 연속된 요소들의 차(*p - a[i - 1])로 설정됩니다.
  6. 배열 b의 각 요소와 배열 a의 해당 요소를 더해 sum에 추가합니다.
  7. 모든 계산이 끝난 후, sum의 값을 printf 함수를 사용하여 출력합니다.

이 문제는 배열과 포인터를 사용하여 주어진 수열의 연산을 수행하고, 그 결과를 합산하는 방법을 이해하는 데 중점을 둡니다. 배열의 요소에 접근하는 다양한 방법과 포인터의 활용을 통한 배열 조작 기법을 숙지하는 것이 중요합니다.

정보처리산업기사 실기 시험 기출 문제 - 문자열 포인터의 활용

문제

다음 C 프로그램이 실행되었을 때의 동작을 설명하고, 출력 결과를 예측하시오.

소스 코드 파일명: string_pointer_usage.c

#include <stdio.h>

main() {
    char *p = "KOREA";
    printf("%s\n", p);
    printf("%s\n", p + 3);
    printf("%c\n", *p);
    printf("%c\n", *(p + 3));
    printf("%c\n", *p + 2);
}

입력 예시

이 프로그램은 사용자로부터 입력을 받지 않습니다.

출력 예시

KOREA
EA
K
E
M

해설

이 프로그램은 문자열을 가리키는 포인터 p를 사용하여 다양한 문자열과 문자 연산을 수행합니다.

  1. char *p = "KOREA";는 문자열 "KOREA"를 가리키는 포인터 p를 선언하고 초기화합니다.
  2. printf("%s\n", p);는 포인터 p가 가리키는 문자열 "KOREA"를 출력합니다.
  3. printf("%s\n", p + 3);는 포인터 p의 주소에서 3을 더해, "EA"를 출력합니다. 즉, 문자열의 네 번째 문자부터 나머지 문자열을 출력합니다.
  4. printf("%c\n", *p);는 포인터 p가 가리키는 첫 번째 문자 'K'를 출력합니다.
  5. printf("%c\n", *(p + 3));는 포인터 p의 주소에서 3을 더한 위치에 있는 문자, 즉 'E'를 출력합니다.
  6. printf("%c\n", *p + 2);는 포인터 p가 가리키는 첫 번째 문자 'K'의 ASCII 코드 값에 2를 더한 값에 해당하는 문자 'M'을 출력합니다.

이 프로그램은 문자열을 가리키는 포인터의 활용법을 보여주며, 포인터 연산을 사용하여 문자열의 특정 부분을 출력하거나 문자를 조작하는 방법을 설명합니다.

정보처리산업기사 실기 시험 기출 문제 - 문자 배열과 포인터

문제

다음 C 프로그램이 실행되었을 때의 동작을 설명하고, 출력 결과를 예측하시오.

소스 코드 파일명: array_and_pointer.c

#include <stdio.h>

main() {
    char a[3][5] = { "KOR", "HUM", "RES" };
    char* pa[] = { a[0], a[1], a[2] };
    int n = sizeof(pa) / sizeof(pa[0]);
    for (int i = 0; i < n; i++)
        printf("%c", pa[i][i]);
}

입력 예시

이 프로그램은 입력을 받지 않습니다.

출력 예시

KUE

해설

이 프로그램은 문자 배열과 포인터 배열을 초기화하고, 포인터 배열을 사용하여 특정 문자를 출력합니다.

  1. char a[3][5] = { "KOR", "HUM", "RES" };는 3행 5열의 문자 배열 a를 선언하고 각 행을 "KOR", "HUM", "RES"로 초기화합니다. 각 문자열 뒤에는 자동으로 널 문자('\0')가 추가됩니다.
  2. char* pa[] = { a[0], a[1], a[2] };는 포인터 배열 pa를 선언하고 a의 각 행의 주소를 초기화하여 pa의 각 원소가 a의 각 행을 가리키게 합니다.
  3. int n = sizeof(pa) / sizeof(pa[0]);는 포인터 배열 pa의 원소 개수를 계산하여 n에 저장합니다. sizeof(pa)는 포인터 배열 전체의 크기를 반환하고, sizeof(pa[0])는 배열의 한 원소, 즉 포인터의 크기를 반환합니다. 이를 나누어 배열의 길이, 즉 3을 얻습니다.
  4. for 루프를 사용하여 pa의 각 원소(즉, a의 각 행을 가리키는 포인터)에 대해 printf 함수를 사용하여 해당 행의 i번째 문자를 출력합니다. 따라서 "KOR"의 첫 번째 문자 'K', "HUM"의 두 번째 문자 'U', "RES"의 세 번째 문자 'E'를 차례대로 출력합니다.

따라서 이 프로그램의 출력 결과는 KUE입니다. 이 프로그램은 문자 배열과 포인터 배열을 사용하는 방법과 배열의 인덱싱을 통해 특정 위치의 데이터에 접근하는 방법을 보여줍니다.

VisualAcademy Docs의 모든 콘텐츠, 이미지, 동영상의 저작권은 박용준에게 있습니다. 저작권법에 의해 보호를 받는 저작물이므로 무단 전재와 복제를 금합니다. 사이트의 콘텐츠를 복제하여 블로그, 웹사이트 등에 게시할 수 없습니다. 단, 링크와 SNS 공유, Youtube 동영상 공유는 허용합니다. www.VisualAcademy.com
박용준 강사의 모든 동영상 강의는 데브렉에서 독점으로 제공됩니다. www.devlec.com