모노산달로스의 행보

[C programming] 문자열을 가리키는 포인터, 포인터 변수 상수화 본문

ProgrammingLanguage/C

[C programming] 문자열을 가리키는 포인터, 포인터 변수 상수화

모노산달로스 2024. 4. 10. 23:39

C programming - 문자열 포인터

 

 

리눅스 환경에서 네트워크 프로그래밍을 공부하기 위해서 C언어를 다시 복습해야 할 필요성을 느꼈습니다. 따라서 이번 기회에 배열부터 전처리기까지 내용들을 정리하겠습니다.

 


문자열이란?

String and Character

 

C언어에서 문자 상수(Character)란 작은 따음표 내에 포함된 하나의 문자를 의미합니다. 만약 여러개의 문자를 한 번에 표현하고 싶다면 어떻게 해야 할까요? 문자 상수를 여러개 저장하는 배열은 어떨까요?

char array[] = {'A', 'B', 'C', 'D'};

 

문자를 저장하는 배열을 만들었습니다. 배열의 요소인 A B C D 각각의 값에 접근하는 것이 가능합니다. 지금부터 공부할 문자열 또한 이러한 문자 배열과 동일하게 동작합니다.

 


 

우리는 이미 문자열(String)을 사용해왔습니다.

#include <stdio.h>
int main(void)
{
    printf("ABCD");
    return 0;
}

 

위와 같이 큰 따옴표 내에 포함된 하나 이상의 문자를 문자열이라고 표현합니다. 중요한 특징으로 문자열의 끝에는 Null 문자가 삽입되어 있습니다. 즉, 종료 문자(\0)가 삽입되어 문자열의 끝을 알립니다.

 

여기서 Null 문자는 포인터에서 사용하는 NULL과는 다른 의미를 가집니다. Null 문자는 종료 문자를 의미하며 ASCII 코드 정수 0에 해당합니다. NULL 포인터는 주소로 0을 의미하며 포인터 변수에 아무런 주소도 저장하지 않겠다는 의미입니다.

 

#include <stdio.h>
int main(void)
{
    char array[] = "ABCD";

    printf("%c %c %c %c %c\n", array[0], array[1], array[2], array[3], array[4]);
    printf("%d %d %d %d %d\n", array[0], array[1], array[2], array[3], array[4]);

    printf("size of array : %d \n", sizeof(array));

    return 0;
}

예제 코드의 실행 결과

 

위에서 설명한 내용을 코드를 통해 알아볼 수 있습니다. 문자열 배열을 선언한 뒤 각각의 문자에 접근합니다. 문자를 출력하는 경우 index 4에 해당하는 문자는 없는 것으로 보입니다. 하지만 ASCII와 배열의 크기를 출력함으로써 Null이 포함되어 있음을 확인할 수 있습니다.

 

이번에는 서식문자 %s를 통해서 문자열을 출력해 보겠습니다.

#include <stdio.h>
int main(void)
{
    char array[] = "ABCD";

    printf("%s\n", array);
    printf("%s\n", array+1);
    printf("%s\n", array+2);
    printf("%s\n", array+3);

    return 0;
}

 

 

서식문자 %s는 문자열의 시작 주소를 통해 문자열을 출력합니다. 문자열을 처음부터 종료 문자를 만날 때 까지 계속해서 출력합니다. 

 

#include <stdio.h>
int main(void)
{
    char array1[] = {'A', 'B', 'C', 'D', '\0'};
    char array2[] = {'A', 'B', 'C', 'D'};

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

    return 0;
}

예제 코드의 실행 결과

 

만약 문자 배열을 서식문자 %s를 사용하여 출력하면 어떻게 될까요? 문자열 배열과 차이점은 종료 문자가 없다는 것입니다. 따라서 서식문자 %s를 종료 위치를 판단하지 못하기 때문에 사용하면 알 수 없는 값이 출력됩니다.

 


포인터와 문자열

#include <stdio.h>
int main(void)
{
   char* p = "ABCD";

   printf("%s\n", p);
   printf("%s\n", p+1);
   printf("%s\n", p+2);
   printf("%s\n", p+3);

    return 0;
}

 

포인터로 문자열의 시작 주소를 저장하여 값을 출력할 수 있습니다. 그렇다면 포인터를 통해 문자열의 값을 수정하는 것은 어떨까요?

 

#include <stdio.h>
int main(void)
{
    char array[] = "ABCD";
    char* p = "ABCD";

    p[0] = 'X'; // error
    array[0] = 'X';

    p = array;
    array = array + 1; // error

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

    return 0;
}

 

문자열 상수를 가리키는 포인터를 통해서 값을 변경하면 에러가 발생합니다.

 

#include <stdio.h>
int main(void)
{
    char* array[2]={"Good morning", "C-language"};
    
    printf("%s \n", array[0]);
    printf("%s \n", array[1]);
    printf("%s \n", array[0]+5);
    printf("%s \n", array[1]+2);

    return 0;
}

예제 코드의 실행 결과

 

위와 같이 포인터 배열을 선언하여 여러개의 문자열을 저장할 수 있습니다.

 


포인터 변수의 상수화

const 키워드를 이용하여 포인터 변수를 상수화 하는 것이 가능합니다. 상수는 값이 변하지 않는 수라는 것을 우리는 이미 알고 있습니다. 이를 포인터에 사용하는 것은 어떨까요? 다음과 같은 두 가지 의미를 가집니다. 포인터 변수에 다른 주소를 저장하지 못하게 하는 것 그리고 포인터 변수를 통해 메모리 공간의 값을 변경하지 못하게 하는 것 입니다.

 

#include <stdio.h>
int main(void)
{
    char a = 'A';
    char b = 'B';

    char* const p = &a;

    *p = 'C';
    p = &b; // error

    return 0;
}

 

위 예제는 포인터 변수에 다른 주소를 저장하지 못하게 상수화 한 예제입니다. *p = 'C'를 통해 변수 a의 값을 바꾸는 것은 허용됩니다. 하지만 포인터에 저장된 주소를 변수 b의 주소로 바꾸면 에러가 발생합니다. const 키워드를 포인터 변수의 이름 p 앞에 사용하였습니다.

 

#include <stdio.h>
int main(void)
{
    char a = 'A';
    char b = 'B';

    const char* p = &a;

    *p = 'C'; // error
    p = &b; 

    return 0;
}

 

위 예제는 포인터 변수를 통해 메모리 공간의 값을 변경하지 못하게 상수화 한 예제입니다. 차이가 보이시나요? const 키워드를 자료형 char* 앞에 사용하였습니다. 이러한 경우 포인터에 저장된 주소를 변경하는 것은 허용되지만 주소에 저장된 값을 바꾸는 것은 불가능합니다.

 

#include <stdio.h>
int main(void)
{
    char a = 'A';
    char b = 'B';

    const char* const p = &a;

    *p = 'C'; // error
    p = &b;  // error

    return 0;
}

 

두 가지 상수화 방식을 모두 사용하는 것 또한 가능합니다.