변수 형태의 문자열
char형의 배열에 문자를 집어넣음으로써 문자열을 저장할 수 있다.
문자열을 입출력할 때 반복문을 사용하여 다음과 같이 코드를 작성할 수도 있다.
#include <stdio.h>
#pragma warning(disable:4996)
int main(void)
{
int i;
char arr[5];
for(i=0;i<4;i++)
{
scanf("%c",&arr[i]);
}
for(i=0;i<4;i++)
{
printf("%c",arr[i]);
}
return 0;
}
하지만 이 방법은 거의 사용하지 않는다.
우리는 %s 서식문자를 사용하면 문자열 입출력을 훨씬 간단하게 작성할 수 있다.
아래 코드는 위 코드와 같은 기능을 하지만 반복문을 필요로 하지 않아서 훨씬 간단한 코드로 작성되었다.
#include <stdio.h>
#pragma warning(disable:4996)
int main()
{
char arr[5];
scanf("%s", arr);
printf("%s", arr);
return 0;
}
%s 서식문자와 scanf 함수를 사용하여 문자열을 입력받을 때에는 &를 사용하지 않아도 된다.
또한 배열에 문자열을 저장할 때에는 저장하고자 하는 문자보다 1개 이상 큰 배열을 만들어야 한다.
문자열의 가장 마지막 문자 뒤에 널문자가 따라붙기 때문이다.
예를 들어 "Have A Nice Day!"라는 문자열을 arr이라는 이름의 배열에 저장하려고 한다면
다음과 같은 형태로 배열에 저장된다.

배열을 사용하여 문자열을 선언할 경우 다음과 같이 초기화하는 것도 가능하다.
char str[] = "Have A Nice Day!";
이때 대괄호 뒤에 배열의 크기를 명시하지 않아도 자동으로 문자열의 크기에 맞게 생성된다.
#include <stdio.h>
int main()
{
char str[] = "Have A Nice Day!";
printf("%s\n%d", str, sizeof(str));
return 0;
}
위 코드를 통해 생성된 배열의 크기가 17Byte라는 것을 알 수 있다.
상수 형태의 문자열
문자열을 선언할 때 포인터를 사용하는 것도 가능하다.
char *ptr = "Hello World!";
그런데 이렇게 포인터를 사용했을 때 문제점이 있다.
바로 일부 컴파일러에서는 값을 변경할 수 없다는 것이다.
배열을 사용할 때와 데이터를 저장하는 방식에 있어서 차이가 있기 때문이다.
배열을 사용하면 위에서 그림으로 설명한 대로 문자열을 하나하나의 문자로 분리하여 메모리에 저장하고 있는 형태이다.
그러나 포인터의 경우 단지 주소를 가리키고 있을 뿐이다.
포인터 변수에 실제로 문자열이 저장되어 있는 것이 아니라
메모리 어딘가에 저장되어 있는 "Hello World!"의 주소를 가리키고 있는 것이다.
포인터 변수의 크기를 확인해 봐도 당연히 "Hello World!"가 들어갈만한 메모리 크기도 할당되어있지 않다.
#include <stdio.h>
int main()
{
char *ptr = "Hello World!";
printf("%s\n%d", ptr, sizeof(ptr));
return 0;
}
포인터 변수 ptr에는 고작 8바이트가 할당되어 있을 뿐이다.
#include <stdio.h>
int main()
{
char *ptr = "Hello World!";
ptr[1] = 'K';
printf("%s", ptr);
return 0;
}
따라서 위 코드를 실행시켜 보면 정상적으로 작동하지 않는 것을 확인할 수 있다.
#include <stdio.h>
int main()
{
char *ptr = "Hello World!";
ptr = "Have A Nice Day!";
printf("%s", ptr);
return 0;
}
그러나 위 코드와 같은 변경은 가능하다.
ptr이 가리키고 있는 주소를 "Hello World!"가 저장된 주소에서 "Have A Nice Day!"가 저장된 주소로
옮겼을 뿐이기 때문이다.
상수와 변수를 결정짓는 요소
그럼 아래와 같은 코드를 작성했다고 해보자.
이 문자열은 상수 형태의 문자열일까? 아니면 변수 형태의 문자열일까?
char *ptr = (char*)malloc(sizeof(char)*50);
scanf("%s", ptr);
위와 같이 선언할 경우에는 변수 형태의 문자열이라고 할 수 있다.
지난 포스팅에서 동적할당으로 배열도 선언할 수 있다고 했었다.
동일한 원리로 동적할당으로 문자열을 선언하는 것 또한 당연히 가능한 것이다.
그렇기 때문에 위 코드는 char형의 50칸짜리 배열이 선언된 것과 동일한 것이다.
따라서 아래와 같은 코드도 정상적으로 동작한다.
#include <stdio.h>
#include <stdlib.h>
#pragma warning (disable:4996)
int main()
{
char *ptr = (char*)malloc(sizeof(char)*50);
scanf("%s", ptr);
ptr[1] = 'K';
printf("%s", ptr);
return 0;
}
char *ptr = (char*)malloc(sizeof(char)*50);
ptr = "Have A Nice Day!";
그러나 위 코드처럼 같은 방법으로 선언하였더라도 포인터변수에 문자열을 대입해 버리면
문자열의 주소가 들어가게 되어서 변경이 불가능하다.
그저 50Byte의 큰 메모리에 "Have A Nice Day!"라는 문자열의 주소를 저장하고 있을 뿐이다.
함수에 값을 전달할 때에도 마찬가지이다.
printf("Have A Nice Day!");
만약 위와 같은 함수를 사용헀다고 해보자.
그렇다면 이 때 전달하는 문자열의 자료형은 어떻게 될까?
char* 형이라고 예상할 수 있을 것이다.
그렇다는 것은 문자열의 모든 정보를 함수에 전달하는 것이 아니라 문자열을 먼저 메모리 상에 저장하고
그 주소만을 전달한다는 것이다.
따라서 printf 함수의 매개변수 선언은 다음과 같이 예상할 수 있을 것이다.
??? printf(char * ptr)
{
// ....
}
따라서 문자열을 어떤 방식으로 선언했냐 보다는 어떤 데이터가 들어왔느냐를 더 중요하게 생각해야 할 것이다.
'C언어 > C언어 문법' 카테고리의 다른 글
[C언어] 리터럴 상수 (Literal Constant) (0) | 2024.02.13 |
---|---|
[C언어] 문자열 배열 (0) | 2024.02.13 |
[C언어] 동적할당 (stdlib.h, malloc, free) (0) | 2024.02.12 |
[C언어] 포인터 연산 (2) | 2024.02.12 |
[C언어] 배열의 주소 (0) | 2024.02.12 |