Skip to content

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];과 같이 크기가 10char 타입 배열을 선언했으므로 s1에는 문자를 10개만 저장할 수 있다.
  • 하지만 문자열 맨 뒤에 붙는 널 문자까지 포함하면 실제로 저장할 수 있는 문자는 9개뿐이다.
  • 따라서 입력할 때 문자열의 개수가 배열의 크기 - 1을 넘지 않도록 한다.


001


  • scanf에서 서식 지정자 %s로 문자열을 저장할 때 입력된 문자열에 공백이 있다면 배열에는 공백 직전까지만 저장된다.
  • 예를 들어 중간에 공백이 있는 Hello, world!를 입력하면 Hello,까지만 저장된다.


공백까지 포함하여 입력받기

  • scanf 함수에서 서식 지정자를 "%[^\n]s"와 같이 지정하면 공백까지 포함하여 문자열을 입력받을 수 있다.


char s1[30];

printf("문자열을 입력하세요: ");
scanf("%[^\n]s", s1); // 공백까지 포함하여 문자열 입력받기

printf("%s\n", s1);

// 문자열을 입력하세요: Hello, world! (입력)
// Hello, world!


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 함수에서 사용할 수 없다.


002


  • 입력 값을 문자열 포인터에 저장하려면 문자열이 들어갈 공간을 따로 마련해야 된다.
  • 따라서 다음과 같이 malloc 함수로 메모리를 할당한 뒤 문자열을 저장한다.


scanf("%s", 문자열포인터);
#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 함수로 동적 할당한 메모리를 해제한다.


003


  • scanf에서 서식 지정자 %s로 문자열을 저장할 때 입력된 문자열에 공백이 있다면 문자열 포인터에는 공백 직전까지만 저장된다.
  • 예를 들어 중간에 공백이 있는 "Hello, world!"를 입력하면 "Hello,"까지만 저장된다.


char s1[10], char *s1 = malloc(sizeof(char) * 10)인데도 9개 넘게 입력받을 수 있던데요?

  • 컴파일러나 OS에 따라서 배열이 선언된 메모리나 동적으로 할당한 메모리 뒷부분에 공간이 더 있을 수도 있다.
  • 그래서 한 두 개까지는 더 입력할 수 있을 것이다.
  • 하지만 많은 문자열을 입력하면 다른 데이터가 있는 메모리 공간을 침범하게 되므로 에러가 발생한다.
  • 따라서 원칙적으로는 배열의 크기 - 1, 메모리 공간 - 1보다 긴 문자열은 입력받을 수 없다.
  • C에서는 다른 메모리 공간을 침범하는 부분까지 책임을 져주지 않기 때문에 모든 부분을 프로그래머가 제어해야 한다.
  • 그래서 C로 프로그램을 작성할 때 항상 입력 값과 크기 부분을 확인하는 습관을 들여야 한다.
  • 예전부터 입력 길이 문제로 인해 버퍼 오버플로우 공격, 스택 오버플로우 공격을 이용한 해킹이 많았다.
  • 실무에서는 보안을 위해 미리 입력 값을 검사하거나 입력 값의 길이를 제한하는 함수를 주로 사용한다.


5. 문자열을 여러 개 입력받기

  • 이번에는 공백으로 구분된 문자열 두 개를 입력받아보자.


scanf("%s %s ...", 배열1, 배열2, ...);
scanf("%s %s ...", 문자열포인터1, 문자열포인터2, ...);
#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 사이를 공백으로 띄워주었지만 공백으로 띄우지 않아도 상관없다.


004


  • 문자열을 더 입력받고 싶다면 다음과 같이 %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); // 문자열 다섯 개 입력받기


  • 문자열 리터럴이 할당된 포인터는 변경할 수 없다는 점만 기억하자.

References