[Warming up C Programming] Chapter 8 : 포인터

eunee22·2023년 7월 15일

Warming-up C Programming

목록 보기
8/10
post-thumbnail

제가 대학교 1학년 때 C언어 수업에서 배운 내용을 교재와 ppt를 중심으로 정리한 내용입니다. (2022.3 ~ 2022.6)
당시에 공부를 위해서 HWP 파일로 정리해 놓은 것을 그대로 올립니다.

대학에 처음 들어와 정리한 내용이라 모든 내용을 담고 싶은 욕심에 정리가 많이 지져분하고 어설픈점 양해 부탁드립니다..!

🍑포인터의 기본

포인터의 기본

  • 포인터 : 주소를 저장하는 변수

    • 주소는 %p로 출력 or %#x로 출력하되 대응하는 변수를 적어줄 때 &붙이기
  • 메모리 : 연속된 바이트의 모음. 각 바이트 구분을 위해 주소(번지) 사용.

  • 메모리 주소 공간 : 메모리 주소가 가질 수 있는 범위.

    • ex) 4바이트 크기의 메모리 주소인 경우 0x00000000번지 ~ 0xffffffff번지 사이의 값. (16진수 1자리는 4bit를 의미하므로)
  • 주소값 자체가 중요한 게 아니라 포인터가 어떤 변수를 가르키는지가 중요

    • 프로그래머는 운영체제와 컴파일러가 할당한 변수의 주소를 사용만 할 뿐 마음대로 정할 수는 없으므로
  • 포인터는 다른 변수를 가르킴. 다른 변수의 주소를 할당 받은 후, 해당 변수를 간접적으로 이용.

  • 포인터는 변수의 이름을 사용할 수 없어도 주소로 변수에 접근 가능한 방법을 제공.

포인터의 선언

  • 데이터형 *변수명; 데이터형 *변수명 = 초기화; 형식으로 선언,
    • * : 포인터 수식어. 그다음에 써주는 변수를 포인터로 만듦.
    • 데이터형 → 포인터가 가르키는 변수의 데이터형. 어떤 형의 변수를 가르키는지에 따라서 포인터의 데이터형 결정.
      • ex) int* 형의 포인터 = int 포인터 = int 변수를 가르킴
  • 데이터형에 관계없이 포인터의 크기는 항상 동일
  • 포인터(주소)의 크기는 플랫폼에 의해서 결정.
    ex) 32bit 플랫폼 → 포인터 크기 4byte
    64bit 플랫폼 → 포인터 크기 8byte

포인터의 초기화

  • 포인터를 초기화하려면 주소가 필요한데, 할당된 변수의 주소를 구해서 사용해야함.
    • 이때, 주소구하기 연산자 &를 이용.
    • 데이터형 *변수명1 = &변수명2;
  • 포인터를 선언 시 어떤 변수의 주소로 초기화 할지 모르면 NULL으로 초기화.
    • 메모리 0번지를 의미하는 것이 아닌 포인터가 어떤 변수도 가리키지 않는단 것 의미.
    • 그렇기에 예외적으로 정수형 상수이지만 포인터형으로 변환가능
    • NULL 대신 0을 사용할 수도 있음.
      • NULL : 널포인터. 표준 C라이브러리에 0으로 정의된 매크로 상수
  • 포인터에 주소 대입시 직접 16진수 주소를 이용하면 정수형 상수로 인식하기에 안됨.
  • (int*)16진수주소를 대입시 컴파일 에러는 나지 않지만, 무엇이 들었는지 모르는 상황에서 포인터로 접근시 실행에러 발생 가능성 존재.

포인터의 표기

  • type 주소 : type형 변수의 주소, type 포인터라는 의미
  • 화살표로 어떤 변수를 가리키는지 나타냄

포인터의 사용

  • c는 포인터와 함께 사용할 수 있게 연산자 제공

1. 주소 구하기 연산자 → & 다음에 써주는 변수의 주소를 구함.

  • & 연산의 결과는 해당 변수의 주소
  • & 연산자는 반드시 변수와 함께 사용해야 하며, 상수나 수식엔 사용 불가.

2. 역참조 연산자 → *

  • 포인터 변수를 선언 후에, 포인터 변수 앞에 *를 쓰면 포인터가 가리키는 변수의 값을 읽어오거나 변경 가능.

  • 연산의 결과는 포인터가 가르키는 변수

    • * 가 할당 연산자 오른쪽에 위치 → 포인터 변수가 참조하는 변수의 메모리 공간
    • *를 단독으로 사용 → 현재 포인터 변수가 참조하는 변수의 데이터
  • 역참조 연산자를 이용하면 변수의 이름을 모르더라도 주소로 해당 변수에 간접적으로 접근가능.

  • 역참조 연산자를 사용하면 포인터가 가르키는 변수가 대신 사용.

  • 일반변수(포인터 변수가 아닌)이나 상수, 수식에는 *를 사용할 수 없음.

    • ex) pa의 주소를 저장하는 포인터 변수일 때, 역참조 연산자를 포인터 변수에 사용하면 *p 대신 a가 사용되는 것처럼 처리.

  • 참조 : 포인터가 다른 변수를 가리키는 것.
  • 역참조 : 포인터가 가리키는 변수를 끌어와서 사용하는 것.

포인터의 용도

  1. 다른 함수의 지역 변수를 변경하려고 할 때
  • 인자를 매개변수로 복사해서 전달하는 경우, 매개변수를(복사본을) 변경해도 원본은 변경X
  1. 변수를 직접 사용 불가 할 때
  • 포인터를 이용해서 주소로 접근 가능 (원본을 직접 변경)
  1. 여러 변수에 공통의 방법으로 접근(공통의 코드 작성)하고 싶을 때
  • 포인터가 어떤 변수를 가리킬지 미리 알수 없는 경우에도 포인터가 가리키는 변수로 코드작성 가능.

포인터 사용 시 주의사항

  1. 어떤 변수도 가리키지 않는 포인터는 널 포인터로 만듦
  • 포인터는 항상 특정변수를 가리키거나 널 포인터로 만듦
  • 널 포인터로 역참조 연산을 수행하면 운영체제가 예외를 발생시키므로 프로그램 강제종료
  1. 포인터와 포인터가 가리키는 변수의 데이터형이 같아야함
  • 포인터형 불일치시 컴파일 경고 발생, 무시하고 실행시 실행 에러 발생

const 포인터

  • 포인터도 const 변수로 선언 가능
  • const의 위치에 따라 의미가 달라짐.
  1. const type *variable; = type const *variable;
  • 포인터가 가리키는 변수의 값 변경 불가 → 읽기전용 포인터(변경 시 컴파일 에러)
  • 포인터 자신의 값(포인터에 저장된 주소)은 변경 가능 → 다른 변수를 가리키게 가능
    • 한마디로 변수가 가리키는 대상은 변경가능, 변수의 내용은 변경불가
  • 포인터가 가리키는 변수를 const 변수인 듯 사용 → 변수의 const 여부와는 상관없이 const 포인터로 접근시에만 const 변수로 사용
    • ex) const int* p1 = &a;aconst 변수 X
      *p1p1으로 역참조한 변수를 const 변수인 듯 사용
  1. type *const variable;
  • 포인터 자신의 값(포인터에 저장된 주소) 변경 불가 → 특정 변수의 전용 포인터
  • 포인터가 가리키는 변수의 값은 변경 가능.
  • 선언 시 특정 변수의 주소로 초기화 해야하며, 초기화 후 다른 변수를 가리킬 수 없음.
    • 널 포인터로 초기화 시 한번 만들어진 다음에는 포인터에 다른 주소를 저장할 수 없기 때문
  1. const type *const variable;
  • 읽기 전용 포인터 + 특정 변수의 전용 포인터
  • 반드시 초기화 해야함.
  • 가리키는 변수의 값, 포인터 자신의 값(포인터에 저장된 주소) 둘다 변경 불가.

🍑배열과 포인터

  • 배열을 포인터처럼 사용가능, 포인터를 배열처럼 사용가능 하지만, 서로 같은 것은 아님

포인터의 연산

  • 포인터에도 연산자를 사용할 수 있음.
  • 포인터에 마치 배열이 있는 것처럼 메모리에 접근
  • 포인터p가 배열 arr를 가리킬 때 *(p+i)arr[i]를 의미

  1. p + N → 주소에 p가 가르키는 데이터형의 크기를 N개만큼 더한 주소
    p – N → 주소에 p가 가르키는 데이터형의 크기를 N개만큼 뺀 주소

  2. p1 – p2 → 포인터가 가르키는 데이터형의 개수로 주소의 차를 구함.
    같은 형의 포인터에 대해서 빼기 연산 수행 시 두 포인터의 주소 차, 즉 가리키는 데이터형이 몇 개 크기만큼 차이가 나는지 구할 수 있음.

  3. p++ → 주소를 포인터가 가르키는 데이터형의 크기만큼 증가 → 포인터가 가리키는 배열의 다음원소를 가리킴.
    p-- → 주소를 포인터가 가리키는 데이터형의 크기만큼 감소 → 포인터가 가리키는 배열의 이전원소를 가리킴.

  4. p1 = p2 → 같은 형의 포인터끼리 대입

  5. p1 == p2 → 주소가 같은지 비교

  6. p1 != p2 → 주소가 다른지 비교

  7. p[N] → 포인터 배열 이름인 것처럼 사용해서 N번째 원소에 접근

  8. *p = p가 가리키는 변수에 접근

  9. &p → p의 주소를 구함

  10. p → m → p가 가리키는 구조체의 멤버 m에 접근

배열처럼 사용되는 포인터

  • 배열 원소를 가리키는 포인터 : type*형의 포인터는 type형의 변수(크기가 1인 type형의 배열으로 볼 수 있음) 또는 type형 배열의 원소를 가리킬 수 있음.
  • 배열 이름을 인덱스 없이 사용 시 배열의 시작주소(0번 원소)를 의미.
    ex) arr = &arr[0]
  • 배열 원소를 가리키는 포인터는 배열처럼 사용할 수 있음.
    ex) p[i] = *(p + I) → 배열 시작 주소에서 배열 원소 I개만큼 떨어진 곳에 있는 int 변수

포인터처럼 사용되는 배열

  • 배열 이름은 배열의 시작 주소이므로 배열 이름을 포인터처럼 사용할 수 있음.

배열과 포인터의 비교

  • 배열 이름은 특정 변수 전용 포인터
  • 배열 이름은 배열의 시작 주소이기에 다른 주소를 대입하거나 이름으로 증감연산을 할순 없음. (다른 값으로 변경 불가)
  • 포인터는 다른 변수의 주소를 저장하는 변수이므로 값을 변경 가능.
  • sizeof(배열명)은 배열 전체의 바이트 크기를 구하지만, sizeof(포인터명)은 포인터 변수의 크기를 구함.

🍑함수와 포인터

함수의 인자 전달 방법

  1. 값에 의한 호출(값 호출)
  2. 참조에 의한 호출(참조 호출)

값에 의한 호출

  • 인자를 매개변수로 복사해서 전달
  • 함수의 매개변수는 함수 호출시 생성되는 지역변수로, 인자의 값으로 초기화.
  • 함수 안에서 복사본인 매개변수를 변경해도 원본인 인자는 변하지 않음.

참조에 의한 호출

  • 인자의 주소를 포인터형의 매개변수로 전달.
    • 변수에 대한 참조를 전달
  • c 에서는 포인터로 참조에 대한 호출을 처리.
  • 매개변수는 인자를 가리키는 포인터로 선언.
  • 함수 호출 시에는 인자로 전달하려는 변수의 주소를 전달
    • 반드시 변수만 인자로 전달 가능
  • 함수 안에서 매개변수(참조)가 가리키는 원본인 인자를 변경 가능.
  • 함수 정의시 포인터형의 매개변수를 역참조해서 매개변수가 가리키는 변수에 접근.

매개변수의 역할에 따른 인자 전달 방법 결정

1. 입력 매개변수 → 값에 의한 호출

  • 데이터형 함수이름(데이터형 변수명, 데이터형 변수명);
  • 함수 안에서 값이 사용될 뿐 변경되지 않음

2. 출력 매개변수 → 참조에 의한 호출

  • 데이터형 함수이름(데이터형 변수명, 데이터형 변수명, 데이터형 *변수명, 데이터형 *변수명);
  • 함수 안에서 값이 사용되지는 않고 함수 리턴전에 변경.
  • 함수의 처리 결과가 2개 이상인 경우에 사용
    • C언어에서 결과가 2개 이상인 경우 해당 함수를 설계 불가하므로 해결 방법으로 사용

  • 출력 매개변수로 함수의 처리 결과를 전달하는 법
  1. 함수의 원형을 정할때는 출력 매개변수를 포인터로 선언

  2. 함수를 호출 시 처리 결과를 받아올 변수의 주소 전달

    • 미리 선언 후, &를 붙여서 전달
  3. 함수 정의 시 포인터형의 매개변수가 가리키는 곳에 처리 결과를 저장.

    • *변수이름 = 값
  4. 입출력 매개변수 → 참조에 의한 호출

  • 데이터형 함수이름(데이터형 *변수명, 데이터형 *변수명);
  • 함수 안에서 값이 사용도 되고, 리턴 전에 변경도 가능.

배열의 전달

  • 함수의 인자가 배열인 경우, 해당 배열의 값을 복사하여 전달하는 것이 아닌 항상 주소를 전달한 후 매개변수인 포인터로 해당 메모리 공간을 사용할 수 있게함.

    • 값에 의한 호출로 전달시 배열 전체를 복사해야 하므로 시간, 공간적 성능 저하 발생
  • C에선 배열을 함수의 인자로 전달할 때 항상 포인터로 전달, 참조에 의한 호출 사용.

  • 배열의 크기는 별도의 매개변수로 받아와야함.

    • 배열이 포인터이기 때문에 sizeof를 사용시 항상 같은 값이 나옴.
  • 함수 호출 시 배열을 전달하려면, 배열 이름(배열의 시작주소)를 전달

  • 함수 안에서는 포인터형의 매개변수를 배열처럼 사용.
    → 배열의 주소를 전달해서 매개변수인 포인터를 이용해서 포인터를 배열처럼 사용한다고 이해.

  • 배열이 입력매개변수 일때는 읽기 전용 포인터로 선언하는 것이 좋음.

    • 함수안에서 배열의 원소를 변경 할수 없게함 → 입출력 매개변수의 구분을 위함
  • 함수의 원형을 정할 때 함수의 매개변수는 배열의 크기를 생략하고 선언하거나 배열 원소에 대한 포인터 형으로 선언.
    데이터형 배열이름[] = 데이터형 *배열이름

profile
보안 공부하는 대학교 4학년 / 시리즈에서 더욱 편하게 글을 찾아보실 수 있습니다:)

0개의 댓글