33. 문자열 입력/저장
1. 키워드
- 버퍼 오버플로우(Buffer Overflow)
- 스택 오버플로우(Stack Overflow)
2. 입력 값을 문자열에 저장하기
- 이번에는 사용자의 입력 값을 문자열에 저장해 보자.
3. 입력 값을 배열 형태의 문자열에 저장하기
scanf
함수에서 서식 지정자로%s
를 사용하면 입력 값을 배열 형태의 문자열에 저장할 수 있다.
#define _CRT_SECURE_NO_WARNINGS // scanf 보안 경고로 인한 컴파일 에러 방지
#include <stdio.h>
int main()
{
char s1[10]; // 크기가 10인 char 타입 배열을 선언
printf("문자열을 입력하세요: ");
scanf("%s", s1); // 표준 입력을 받아서 배열 형태의 문자열에 저장
printf("%s\n", s1); // 문자열의 내용을 출력
return 0;
}
// 문자열을 입력하세요: Hello (입력)
// Hello
scanf("%s", s1);
과 같이scanf
함수에서 서식 지정자로%s
를 넣어서 문자열을 입력받을 수 있도록 설정한다.- 그리고 두 번째 매개변수에는 입력 값을 저장할 배열을 넣는다.
- 단, 배열도 포인터처럼 주소를 담고 있으므로 일반 변수와 달리 배열 앞에는
&
를 붙이지 않는다.
int num1;
char s1[10];
scanf("%d", &num1); // 일반 변수일 때는 변수 앞에 &를 붙임
scanf("%s", s1); // 배열은 앞에 &를 붙이지 않음
char s1[10];
과 같이 크기가10
인char
타입 배열을 선언했으므로s1
에는 문자를10
개만 저장할 수 있다.- 하지만 문자열 맨 뒤에 붙는 널 문자까지 포함하면 실제로 저장할 수 있는 문자는
9
개뿐이다. - 따라서 입력할 때 문자열의 개수가
배열의 크기 - 1
을 넘지 않도록 한다.
scanf
에서 서식 지정자%s
로 문자열을 저장할 때 입력된 문자열에 공백이 있다면 배열에는 공백 직전까지만 저장된다.- 예를 들어 중간에 공백이 있는
Hello, world!
를 입력하면Hello,
까지만 저장된다.
공백까지 포함하여 입력받기
scanf
함수에서 서식 지정자를"%[^\n]s"
와 같이 지정하면 공백까지 포함하여 문자열을 입력받을 수 있다.
EOF
- EOF는 End Of File의 약자인데 더이상 값을 읽을 수 없는 상태를 나타낸다.
- 콘솔에서는 다음 키의 입력을 EOF로 정해놓았다.
1] Windows
Ctrl + Z
2] 리눅스
Ctrl + D
int c1 = getchar(); // 문자를 입력받음
printf("%d\n", c1);
printf("%d\n", EOF);
// ^Z (Ctrl + Z 입력)
// -1
// -1
- EOF는
stdio.h
헤더 파일에 정의되어 있으며 정수-1
이다. - 보통 EOF는 파일 처리 함수가 실패했을 때 반환된다.
4. 입력 값을 문자열 포인터에 저장하기
- 이번에는 문자열 포인터에 사용자의 입력 값을 저장해 보자.
#define _CRT_SECURE_NO_WARNINGS // scanf 보안 경고로 인한 컴파일 에러 방지
#include <stdio.h>
int main()
{
char *s1 = "Hello"; // 문자열 포인터를 선언하고 문자열 할당
printf("문자열을 입력하세요: ");
scanf("%s", s1); // 실행 에러
printf("%s\n", s1);
return 0;
}
char *s1 = "Hello";
와 같이 문자열 포인터를 선언한 뒤scanf
함수로 입력 값을 문자열 포인터에 저장했다.- 얼핏 보면 저장이 될 것 같지만 실행을 해보면 에러가 발생한다.
- 왜냐하면
s1
에 저장된 메모리 주소는 읽기만 할 수 있고, 쓰기가 막혀있기 때문이다. - 따라서
s1
과 같이 문자열 리터럴의 주소가 할당된 포인터는scanf
함수에서 사용할 수 없다.
- 입력 값을 문자열 포인터에 저장하려면 문자열이 들어갈 공간을 따로 마련해야 된다.
- 따라서 다음과 같이
malloc
함수로 메모리를 할당한 뒤 문자열을 저장한다.
#define _CRT_SECURE_NO_WARNINGS // scanf 보안 경고로 인한 컴파일 에러 방지
#include <stdio.h>
#include <stdlib.h> // malloc, free 함수가 선언된 헤더 파일
int main()
{
char *s1 = malloc(sizeof(char) * 10); // char 타입 10개 크기만큼 동적 메모리 할당
printf("문자열을 입력하세요: ");
scanf("%s", s1); // 표준 입력을 받아서 메모리가 할당된 문자열 포인터에 저장
printf("%s\n", s1); // 문자열의 내용을 출력
free(s1); // 동적 메모리 해제
return 0;
}
// 문자열을 입력하세요: Hello (입력)
// Hello
char *s1 = malloc(sizeof(char) * 10);
처럼char
타입10
개 크기만큼 동적으로 메모리를 할당했다.- 이렇게 하면
scanf
함수로 입력 값을 문자열 포인터s1
에 저장할 수 있다. - 그리고
scanf
를 사용할 때s1
은 포인터이므로&
를 붙이지 않고 그대로 넣는다.
char *s1 = malloc(sizeof(char) * 10); // char 타입 10개 크기만큼 동적 메모리 할당
printf("문자열을 입력하세요: ");
scanf("%s", s1); // 포인터는 &를 붙이지 않고 그대로 넣음
- 문자열 사용이 끝났다면 반드시
free
함수로 동적 할당한 메모리를 해제한다.
scanf
에서 서식 지정자%s
로 문자열을 저장할 때 입력된 문자열에 공백이 있다면 문자열 포인터에는 공백 직전까지만 저장된다.- 예를 들어 중간에 공백이 있는
"Hello, world!"
를 입력하면"Hello,"
까지만 저장된다.
char s1[10]
, char *s1 = malloc(sizeof(char) * 10)
인데도 9
개 넘게 입력받을 수 있던데요?
- 컴파일러나 OS에 따라서 배열이 선언된 메모리나 동적으로 할당한 메모리 뒷부분에 공간이 더 있을 수도 있다.
- 그래서 한 두 개까지는 더 입력할 수 있을 것이다.
- 하지만 많은 문자열을 입력하면 다른 데이터가 있는 메모리 공간을 침범하게 되므로 에러가 발생한다.
- 따라서 원칙적으로는
배열의 크기 - 1
,메모리 공간 - 1
보다 긴 문자열은 입력받을 수 없다. - C에서는 다른 메모리 공간을 침범하는 부분까지 책임을 져주지 않기 때문에 모든 부분을 프로그래머가 제어해야 한다.
- 그래서 C로 프로그램을 작성할 때 항상 입력 값과 크기 부분을 확인하는 습관을 들여야 한다.
- 예전부터 입력 길이 문제로 인해 버퍼 오버플로우 공격, 스택 오버플로우 공격을 이용한 해킹이 많았다.
- 실무에서는 보안을 위해 미리 입력 값을 검사하거나 입력 값의 길이를 제한하는 함수를 주로 사용한다.
5. 문자열을 여러 개 입력받기
- 이번에는 공백으로 구분된 문자열 두 개를 입력받아보자.
#define _CRT_SECURE_NO_WARNINGS // scanf 보안 경고로 인한 컴파일 에러 방지
#include <stdio.h>
int main()
{
char s1[10]; // 크기가 10인 char 타입 배열을 선언
char s2[10]; // 크기가 10인 char 타입 배열을 선언
printf("문자열을 두 개 입력하세요: ");
scanf("%s %s", s1, s2); // 표준 입력에서 공백으로 구분된 문자열 두 개를 입력받음
printf("%s\n", s1); // s1의 내용을 출력
printf("%s\n", s2); // s2의 내용을 출력
return 0;
}
// 문자열을 두 개 입력하세요: Hello world (입력)
// Hello
// world
- 먼저 문자열 두 개를 저장하기 위한 공간을 선언한다.
- 여기서는
s1
,s2
배열을 선언했다. - 물론 문자열 포인터에 메모리를 할당해도 된다.
- 이제
scanf("%s %s", s1, s2);
와 같이 서식 지정자로%s
를 두 개 넣어준다. - 그리고
%s
에 해당하는 문자열이 저장될 배열을 두 개를 넣어주면 공백으로 구분된 문자열을 입력받을 수 있다. - 여기서는
%s
사이를 공백으로 띄워주었지만 공백으로 띄우지 않아도 상관없다.
- 문자열을 더 입력받고 싶다면 다음과 같이
%s
와 입력받을 배열(또는 문자열 포인터)의 개수를 늘려준다.
char s1[10], s2[10], s3[10], s4[10], s5[10];
scanf("%s %s %s", s1, s2, s3); // 문자열 세 개 입력받기
scanf("%s %s %s %s", s1, s2, s3, s4); // 문자열 네 개 입력받기
scanf("%s %s %s %s %s", s1, s2, s3, s4, s5); // 문자열 다섯 개 입력받기
- 문자열 리터럴이 할당된 포인터는 변경할 수 없다는 점만 기억하자.