포인터, 포인터 배열

ChoRong0824·2022년 10월 8일
2

C

목록 보기
4/17

저는 c언어 초보자입니다. java를 모국어로 1개 언어만 파는 바보였습니다.
이에 c언어를 잘 몰랐어서, 포인터 part를 따로 공부해야 할 정도였습니다.
아직도 완벽하진 않지만, 그래도 약간이나마 도움이 되고자 이렇게 다시 포인터에 대해서 포스팅 하게 되었습니다.



포인터

주소

  • c의 주소란 메모리의 장소를 직접 나타내기 위해 사용되는 메모리 상의 주소
  • 변수의 값이 저장되어 있는 메모리의 주소를 알려면 주소 연산자의 &를 사용한다.

&변수명 : 지정된 변수가 컴퓨터의 메모리 상의 어디에 저장되어 있는가를 나타내는 정보(주소)를 계산

  • &a의 값을 보면 1768400으로 출력되어 있는 것을 알 수 있다. 이 경우 변수 a의 값은 그림과 같이 1768400 이라는 메모리의 위치에 저장되어 있는 것을 알 수 있다.
  • 여기서는 1768400 이라는 값이 출력 되었지만, 주소의 값은 사용하는 환경과 프로그램의 실행 상황에 따라 바뀝니다.
  • 변수의 주소의 개수가 중요한 것이 아니고, 주소를 사용해서 메모리 상의 위치를 나타낼 수 있다는 것입니다.

    (주소 출력 / 값 출력) 의 차이점을 명확히 구분하기

  • &a : 주소 출력
  • a : 값 출력

포인터 변수

  • 포인터는 기본 데이터형에서 유도된 데이터형으로 어떤 변수의 값이 기억되어 있는 위치를 다루기 위해 사용되며, -> 데이터가 기억되어 있는 기억 장소의 주소를 기호 주소 형태로 나타낸 것입니다.
  • 기억된 데이터의 값을 참조하기 위하여 변수를 사용하여 직접 참조할 수도 있으나, 포인터를 통하여 간접적으로 참조할 수도 있습니다.
  • 포인터 변수다른 변수를 가리키는 변수이며, 주소를 저장하기 위해서 주소 크기만큼 메모리를 사용하는데 주소의 크기는 시스템에 따라 다릅니다.
    -> 몇 비트의 운영체제 냐의 따라 주소가 다릅니다.
    또한, 포인터는 주소를 이용해서 특정 변수에 접근할 수 있도록 해줍니다.

예) //포인터 선언
정수형 : int *p
문자형 : char *p;

  • 포인터 변수도 일반 변수처럼 변수를 사용하기 전에 선언을 해야한다.

  • 포인터를 선언할 때, 변수 앞에 간접 연산자(*) 를 붙이고, 데이터형은 포인터 변수가 가리키는 메모리에 보관될 데이터형과 일치시킨다.
    데이터형 *포인터 변수명;

  • 정수형 포인터 변수는 저장된 기억 장소의 주소를 가질 수 있다.
    p1=&a;
    -> 변수 a에 저장된 주소를 포인터 변수 p1에 저장하라는 명령이며,
    따라서 p1의 값은 a의 주소인 30f770이 된다.

b = *p1;

  • *p1이란 포인터 변수 p1이 가리키는 기억 장소
    즉, 30f770 번지의 내용을 의미한다.
  • 여기서, p1은 정수형 포인터 변수이므로 *p1은 30f770의 4바이트에 저장된 값 20을 정수형 변수 b에 저장한다.

9.3 예제

#include <stdio.h>

int main(void){

   int a,b,c;
   int *p1, *p2;

   a=0x20, b=0x30, c=0;

   p1=&a; p2= &b;
   c= *p1 + *p2;
   printf("%0x(a)+ %0x(b) = %0x(c)\n",a,b,c);
   printf("a의 주소= %0x, b의주소= %0x, c의 주소= %0x\n", &a,&b,&c);
   printf("*p1=%0x, *p2=%0x\n", *p1, *p2);
   printf("*p1=%0x, *p2=%0x\n", p1, p2);

}

9.4 포인터가 가리키는 변수의 값 변경

#include <stdio.h>

int main(void){

  int a=10;
  int *p1;

  p1 = &a;
  printf("초기값 a = %d\n",a);

  *p1= 30 ;
  printf("변경후의 값 a=%d\n",a);

}

9.5 <char형과 포인터>

#include <stdio.h>

int main(void){
    char*p1;
    p1 ="PAK";

    printf("p1의 주소 = %u p1의 내용 = %u\n", &p1, p1);
    printf("%u  %u %u\n", &(*p1), &(*(p1+1)), &(*(p1+2)));
    printf("%c %c %c\n", *p1, *(p1+1), *(p1+2));
    printf("%c %c %c\n", p1[0], p1[1], p1[2]);
}


9.6 포인터 연산과 문자열의 출력

#include <stdio.h>

void main(void){
    char*d ="12345";
    printf("%c,,%s\n", *d,d);

    d= d+1;

    printf("%c,,%s\n", *d,d);
    printf("%c,,%c\n", *(d-1), *(d+1));
    
}


  • 포인터와 배열은 밀접한 관계를 가지고 있다. 배열이 기억 장소에 저장될 때 원소들은 연속된 기억 장소에 저장된다.
    그리고 배열명은 배열의 첫 번째 원소가 저장된 주소를 가리키는 포인터 변수이며, 배열명을 기준 주소(base adress)로 정하고 여기에 몇 번째 원소인가를 나타내는 숫자를 더하여 배열 원소를 포인터로 나타낼 수 있다.

  • 아래의 그림과 같이 배열의 최초의 요소 a[0]를 *p로 표시했을 때, 다음의 포인터 배열의 요소 a[1]은 *p(+1)로 된다.
    ->
    정수형 포인터 변수 p에 주소 0x1000__가 저장되었다고 가정하면 다음과 같이 16진수로 a[1]의 주소는 0x1004, a[2]는 0x1008이 된다.



  • 배열 요소의 크기는 데이터형에 따라 달라질 수 있으나 요소의 순서는 일정합니다.

  • 문자형 데이터의 길이는 1바이트이고, 정수형 데이터의 길이는 4바이트, 실수형(float) 데이터의 길이는 4바이트이다.
    예)

char c [4]  {'a','b','c','d'};
int a [4]  {'1','2','3','4'};
float r [4]  {'1.1','2.2','3.3','4.4'};
  • 각 배열의 세 번째 요소 c[2],a[2],r[2] 는 각각 c+2,a+2,r+2 등과 같은 포인터 형태로 나타낼 수 있으며,이들 배열에는 다음과 같은 관계가 성립합니다.




  • 1 차원 배열로의 포인터의 할당
    • 변수에 포인터를 할당하는 방법과 같이 배열에 대해서도 포인터를 할당할 수 있다.
  • 또한 p는 포인터 변수이므로 p= p+1, p++의 대입 연산이 가능하므로 다음의 프로그램과 같은 관계가 성립합니다.

#include<stdio.h>
int main(void)
{
    int a[5]= {5,10,15,20,25};
    int *p = a;

    printf("%d, %d, %d, %d\n", p[0], *p, p[-1], *(p-1));
    printf("%d, %d, %d, %d\n", p[1], a[1], *(p+1), *(a+1));

    p++;
    printf("%d, %d, %d, %d\n", p[0], *p, p[-1], *(p-1));
    printf("%d, %d, %d, %d\n", p[1], a[1], *(p+1), *(a+1));
}


  • 1차원 배열로 포인터 할당
#include<stdio.h>
int main(void)
{
    int a[]= {1,2,3,4};
    int *p;
    int i, sum =0;

    p=a;
    for(i=0;i<4;i++,p++){
        sum+=*p;
        printf("a[%d]의 주소 =%x\n", i ,p);
        // %x 는 16진수임.
    }printf("\n배열 원소의 합 =%d\n", sum);
}
  • %x 는 16진수를 표현함.



  • 배열 변수의 번지와 포인터의 관계
#include<stdio.h>
int main(void)
{
    int *p1, a[2];
    float *p2, b[2];

    p1=a;
    p2=b;

    printf("p1의 주소 :%u\n p2 주소 : %u\n", &p1,&p2);
    printf("p1의 값 :%u\n p2 주소 : %u\n", p1,p2);
    printf("a 주소 : %u\nb 주소 : %u\n", &a,&b);

    *p1 =1;
    *(p1+1) =2;
    *p2=(float)1.5;
    *(p2+1)=(float)2.5;

    printf("%6d%6d%6.1f%6.1f\n", *p1, *(p1+1),p2,*(p2+1));
    printf("%6d%6d%6.1f%6.1f\n", a[0],a[1],b[0],b[1]);
    printf("%6d%6d%6.1f%6.1f\n", p1[0],p1[1],p2[0],p2[1]);
}


(해석)

  • 7~8행에서 배열 a의 시작 주소 (a[0])가 p1에, 배열 b의 시작 주소 (b[0]) 가 p2에 저장된다.
  • 14~17행, p1 =1;은 p1이 가리키는 주소 (3144764, 즉 &a[0] 또는 a)의 내용인 a[0]에 1을 저장하고, (p1+1)=2;는 주소 (3144764 +4, 즉 3144768)의 내용인 a[1]에 2를 저장한다. p2 =(float)1.5; (p2+1)=(float)2.5; 도 b[0],b[1]에 각각 1.5,2.5 값을 저장한다.
  • 19~20 행, p1, a[0], p1[0] 은 배열과 포인터의 같은 표현 방법으로 모두 1을 저장하고 잇으며, 마찬가지로 p2, b[0], p2[0]도 같은 표현 방법이다.
    a[1][0] == p[0] == (p+0) == a[1] == **(a+1) 로 된다


포인터 배열과 문자열

  • 포인터 배열은 서로 호환성이 있어 포인터 선언 시에 배열을 사용할 수 있고, 배열을 선언할 때에도 포인터를 사용할 수 있는데, 포인터를 요소로 하는 배열을 포인터 배열 이라고 합니다.
    포인터 배열은 포인터를 배열 요소로 하며, 선언 형식은 다음과 같다.
    데이터형*배열명[원소의크기]
  • 이것은 배열을 선언하는 것과 비슷하다. 예를 들어,
int*p[3];
char *a[10];

과 같이 선언할 수 있다. 여기서 포인터 배열 p는 3개의 요소로 이루어지며 각 원소는 정수형 데이터를 가리키는 포인터이다. 또한 a는 10개의 요소로 이루어지며, 각 원소는 문자형 데이터를 가리키는 포인터이다.


재정리

c언어의 꽃, 포인터(*) ;



#include <stdio.h>
struct student{
    char name [12];
    int kor, math, sum, total;};

int main(){
    struct student s[3]={{"홍길동",90,85},{"이순신",88,91},{"아무개",80,75}};
    struct student *p;

    p = &s[0];
    (p+1)-> sum = (p+1)->kor +(p+2)->math;
    (p+1)-> total = (p+1)-> sum+p->kor +p->math;

    printf("%d\n", (p+1)->sum + (p+1)-> total);
}
profile
컴퓨터공학과에 재학중이며, 백엔드를 지향하고 있습니다. 많이 부족하지만 열심히 노력해서 실력을 갈고 닦겠습니다. 부족하고 틀린 부분이 있을 수도 있지만 이쁘게 봐주시면 감사하겠습니다. 틀린 부분은 댓글 남겨주시면 제가 따로 학습 및 자료를 찾아봐서 제 것으로 만들도록 하겠습니다. 귀중한 시간 방문해주셔서 감사합니다.

0개의 댓글