[C] 문자열과 포인터, 포인터 배열

방법이있지·2025년 6월 9일
post-thumbnail

포인터는 문자열 상수를 가리킬 수 있습니다. 여러 문자열을 처리할 때, 문자열을 가리키는 포인터들을 배열로 한꺼번에 저장하면 편리합니다.

char *형 포인터

문자열 상수

printf("%s", "lgtwins");
// [출력] lgtwins

  • 컴파일러는 문자열 상수를 char형 배열 형태로 메모리에 별도로 보관
  • 이후 문자열 상수는 char형 배열의 첫 요소의 주소로 해석됨
    • 이를 문자열의 시작 주소라고도 함
  • 컴파일러는 "lgtwins"char형 배열로 별도 저장
    • 이후 "lgtwins"는 배열의 첫 요소인, 문자 l의 주소로 해석됨
    • printf는 해당 주소부터 시작하는 문자열을 출력
printf("첫 번째 문자: %c\n", *"lgtwins");
printf("두 번째 문자: %c\n", *("lgtwins" + 1));

// "lgtwins"[2]는 *("lgtwins" + 2)와 동일 연산
printf("세 번째 문자: %c\n", "lgtwins"[2]);


// [출력]
// 첫 번째 문자: l
// 두 번째 문자: g
// 세 번째 문자: t
  • 문자열 상수는 주소로 해석되므로
    • * 연산자로 해당 주소의 문자에 접근하고
    • 포인터 연산으로 다음 문자의 주소로 이동할 수 있음
    • 배열명처럼 인덱스로 접근하는 것도 가능
  • 단, *"lgtwins" = 'L' 처럼 문자열 상수의 값을 직접 수정할 순 없음
    • 문자열 상수는 읽기 전용 메모리에 저장되므로, 값 변경은 허용되지 않음

🤔 한국어 문자열도 위 코드처럼 주소 단위로 접근할 수 있나요?

printf("첫 번째 문자: %c\n", *"엘지트윈스");
printf("두 번째 문자: %c\n", *("엘지트윈스" + 1));
printf("세 번째 문자: %c\n", "엘지트윈스"[2]);
printf("전체 문자열: %s\n", "엘지트윈스");

// [출력]
// 첫 번째 문자: �
// 두 번째 문자: �
// 세 번째 문자: �
// 전체 문자열: 엘지트윈스
  • 한글 문자열에서 개별 문자에 포인터 연산으로 접근하려고 하면, 문자가 깨져 보입니다.
  • 알파벳 한 글자는 1바이트이기 때문에, char형 배열에 문자당 1바이트씩 저장됩니다.
  • 하지만 한글 한 글자는 UTF-8 기준 3바이트를 차지합니다.
    • C에서 "엘지트윈스"의 시작 주소를 char 단위로 해석하면,
    • 첫 글자 "엘" 의 3바이트 중 첫 1바이트만 읽게 됩니다.문자열의 시작 주소를 가리킵니다.
    • 이 바이트는 완전한 글자가 아니기 때문에, %c로 출력하려고 하면 깨진 문자가 출력됩니다.
  • 물론 문자열 전체를 출력할 때는, 연속된 3바이트를 한 글자로 처리하기 때문에 깨지지 않습니다.

char *형 포인터

  • 문자열 상수는 문자열의 시작 주소로 해석되므로, 이를 char *형 포인터에 대입할 수 있음
  • 이후 포인터 연산을 통해 이후 문자에 접근할 수 있음
char *pc = "LG Twins";      // pc는 문자열 배열의 시작 주소를 가리킴
printf("2025년 한국시리즈 우승팀은 %s입니다!\n", pc);
pc = "Austin Dean";         // pc는 변수, 새로 대입 가능
printf("MVP는 %s 선수입니다!\n", pc);

// [출력]
// 2025년 한국시리즈 우승팀은 LG Twins입니다!
// MVP는 Austin Dean 선수입니다!

  • printfpc가 가리키는 문자를 출력
    • 이후 포인터 연산을 통해 다음 문자의 주소로 이동
    • 문자열 끝을 나타내는 널 문자(\0)를 만날 때까지 반복
  • 포인터는 변수이므로, 다른 문자열을 대입 가능
  • 장점: char형 배열은 선언할 때 크기를 명시해야 했지만 (char arr[20]), char *형 포인터는 크기 상관없이 문자열 상수만 대입하면 바로 사용 가능

⚠️주의할 점

// 올바른 사용법
char arr[20];
scanf("%s", arr);

// 잘못된 사용법
char *pc;
scanf("%s", pc);
  • 초기화되지 않은 포인터에 scanf("%s", pc)와 같이 직접 값을 입력할 수는 없습니다.
  • 위 코드에서는 pc가 유효한 메모리 공간을 가리키지 않아, 오류가 발생합니다.
    • 이후에 배우게 될 malloc을 사용해, pc가 가리킬 메모리 공간을 먼저 할당해야 합니다.
  • char형 배열은 선언과 동시에 메모리 공간이 할당되므로, 에러가 발생하지 않습니다.

⚠️주의할 점 2

// 올바른 사용법
char arr[20] = "hello";
scanf("%s", arr);

// 잘못된 사용법
char *pc = "hello";
scanf("%s", pc);
  • pc는 문자열 상수 "hello"를 가리킵니다.
  • 문자열 상수는 읽기 전용 메모리에 저장되므로, scanf로 해당 메모리의 내용을 덮어쓰려 하면 오류가 발생합니다.
    • 당연하지만 strcpy도 안 됩니다!!!
  • char형 배열의 경우 문자열 상수를 복사한 뒤 수정 가능한 메모리 공간에 저장하므로, 이후에 내용을 덮어씌워도 문제가 없습니다.

char *형 포인터 배열

  • 포인터도 변수이므로, 여러 포인터를 포인터 배열에 한 번에 저장할 수 있음
  • 특히 char *형 포인터의 배열은, 여러 개의 문자열을 처리할 때 사용 가능
char *parr[4];       // 포인터 배열 선언

// 각 char*형 포인터에 문자열 대입
parr[0] = "hello";  // 별도로 저장된 배열의 첫 주소 대입
parr[1] = "mr.";
parr[2] = "my";
parr[3] = "yesterday";

for (int i = 0; i < 4; i++){
    printf("%s ", parr[i]);
}

// [출력] hello mr my yesterday

  • 선언: 배열명 앞에 *을 붙임
    • char *형 포인터 4개를 갖는 행렬이므로, char *parr[4]로 선언
    • 모든 배열 요소가 char *형 포인터인 메모리 공간이 할당됨
  • parr의 각 요소는, 개별 문자열의 시작 주소
    • 따라서 printf로 각 문자열 출력 가능
char *parr[4] = {"hello", "mr.", "my", "yesterday"};
char arr[4][15] = {"hello", "mr.", "my", "yesterday"};
  • 위와 같이 char*형 포인터 배열을 초기화할 수 있음
  • 2차원 char형 배열의 초기화와 유사하지만, 실제 저장 방식은 다름
    • parr의 각 요소에는, 별도로 저장된 문자열의 시작 주소만 저장됨
    • arr의 각 요소에는, 문자열 전체가 저장됨

포인터 배열을 2차원 배열처럼 사용하기

  • 여러 개의 1차원 배열을 포인터로 가리키고, 포인터들을 배열로 묶을 수 있음
// 1차원 배열 선언
int arr1[4] = {15, 8, 35, 17};
int arr2[4] = {10, 47, 59, 4};
int arr3[4] = {5, 11, 13, 16};

// 포인터 배열에 각 배열의 시작 주소 저장
int *parr[3] = {arr1, arr2, arr3};

// 2차원 배열처럼 출력
for (int i = 0; i < 3; i++){
    for (int j = 0; j < 4; j++){
        printf("%5d", parr[i][j]);
    }
    printf("\n");
}

// [출력]
//    15    8   35   17
//    10   47   59    4
//     5   11   13   16

  • 배열명 arr1, arr2, arr3은, 각 배열의 시작 주소
    • int형을 가리키므로, int *형 포인터 배열인 parr에 저장할 수 있음
  • parr[i][j]: 포인터 연산을 통해 값에 접근
  • e.g., arr3이 300번지부터 할당됐을 때, parr[2][2] 출력하기
    • parr[2]arr3의 시작 주소 300을 가리킴
    • parr[2][2] -> *(parr[2] + 2)로 해석됨
    • parr[2]는 4바이트 int형을 가리키므로, parr[2] + 2 -> 300 + 2 * 4 -> 308
    • *(308)으로 arr3[2]13에 접근

연습문제

다음과 같은 코드가 있을 때, 보기 중 배열 요소의 사용이 잘못된 것을 고르세요.

char *pa = "apple";     // char *형 포인터
char ary[] = "banana";  // char형 배열
char *pary[4];          // char *형 포인터 배열
  1. pary[1] = pa;
  2. pary[2] = ary;
  3. pary[3] = "mango";
  4. pary[4] = "orange";
  • parychar *형 포인터의 배열이므로, 배열의 원소는 char *형이여야 함.
  • 즉 문자열 상수, char 배열의 이름, char *형 포인터 변수가 들어갈 수 있음.
  • (1) pachar *형 포인터. -> 옳은 설명
  • (2) arychar형 배열명이며, 첫 번째 요소의 주소로 해석되므로 char *형. -> 옳은 설명
  • (3)"mango"는 문자열 상수로, 문자열은 char형 배열 형태로 별도로 저장됨. 이때 "mango"는 해당 배열의 첫 번째 요소의 주소로 해석되므로 char *형. -> 옳은 설명
  • (4) pary의 배열 요소는 4개이므로, 인덱스 0-3까지 사용할 수 있음. pary[4]는 할당된 범위를 벗어남. -> 틀린 설명
profile
뭔가 만드는 걸 좋아하는 개발자 지망생입니다. 프로야구단 LG 트윈스를 응원하고 있습니다.

0개의 댓글