C(9): 문자열

이규현·2024년 12월 6일

https://www.notion.so/12-11bd2c198d5980ebb343f42f90493edb?pvs=4

문자열

문자열& 포인터 이해하기

 char* get_apple(){
  char* p = "apple";
  return p;
}

int main(){
  char* str = get_apple();
  printf("%s\n", str);

  return 0;
}

1. get_apple 함수


char* get_apple() {
  char* p = "apple";
  return p;
}
  • 이 함수는 문자열 리터럴 "apple"의 주소를 가리키는 포인터 p를 반환합니다.
  • 문자열 리터럴은 프로그램이 실행될 때 읽기 전용 메모리 영역(텍스트 영역)에 저장됩니다.
    • 예: "apple"의 시작 주소를 p가 가리키고 있습니다.
  • 이 함수는 문자열 자체가 아니라 포인터(p)의 값을 반환합니다.

2. main 함수


int main() {
  char* str = get_apple();
  printf("%s\n", str);

  return 0;
}
  • get_apple 함수가 반환한 문자열 리터럴의 주소를 str에 저장합니다.
  • printf("%s\n", str);str이 가리키는 문자열 "apple"을 출력합니다.
  • 이 프로그램은 정상적으로 동작하며, 결과적으로 "apple"이 출력됩니다.

Reverse Character

#define MAX 15
int main() {
  char ch, str[MAX];
  int i;

  printf("Enter a sentence to reverse\n");
  for(i = 0; (ch = getchar()) != '\n'; i++)
    str[i] = ch;
  str[i] = 0;

  for(--i; i>=0; i--)
    putchar(str[i]);

  return 0;
}

2. main 함수

char ch, str[MAX];
int i;
  • char ch: 한 번에 한 문자를 저장하기 위한 변수입니다.
  • char str[MAX]: 최대 15개의 문자를 저장할 수 있는 배열입니다. 입력받은 문자열이 여기에 저장됩니다.
  • int i: 배열의 인덱스를 관리하는 변수입니다.

3. 첫 번째 for 루프: 문자열 입력

for(i = 0; (ch = getchar()) != '\n'; i++)
    str[i] = ch;
str[i] = 0;
  • getchar()를 사용하여 한 문자씩 입력받습니다.
  • 입력받은 문자를 배열 str의 각 위치에 저장합니다.
  • 반복 조건:
    • 입력받은 문자가 개행 문자('\n')가 아니면 계속 입력을 받습니다.
  • 루프 종료 후:
    • 마지막에 NULL 문자(\0)를 추가하여 문자열의 끝을 표시합니다.
  • 입력 제한:
    • 배열 크기가 MAX로 제한되어 있어 입력 길이가 14자를 초과하면 버퍼 오버플로우 위험이 있습니다.

4. 두 번째 for 루프: 문자열 역순 출력

for(--i; i >= 0; i--)
    putchar(str[i]);
  • i: 첫 번째 루프 종료 후 i는 문자열 끝을 가리키므로 1 감소시켜 마지막 유효 문자를 가리키도록 합니다.
  • i >= 0: 배열의 첫 번째 문자가 포함되도록 조건 설정.
  • putchar(str[i]): 배열을 역순으로 순회하며 각 문자를 출력합니다.
  • 정리: 시작 시점에서 str[i]는 널 문자(0)를 가리킵니다. 따라서 마지막 유효 문자를 가리키기 위해서는 --i가 필요합니다.
  • 이것이 루프가 - -i 로 시작하는 이유입니다.

소문자 → 대문자 변환

#define MAXCHAR 20
#include<ctype.h> // for islower, toupper

char* read_line(){//입력문장을 text 배열에 넣고 시작 주소를 돌려준다.
  char ch;
  int i;
  static char text[MAXCHAR];
  //함수를 빠져나가도 사라지지 않게 하기 위해 정적 변수로 선언

  printf("Enter a sentence.\n");
  for(i=0; (ch = getchar()) != '\n'; i++){
    text[i] = ch;
  }
  text[i] = '\0'; //마지막 NULL로 설정할려고. text[i]는 NULL 가리키겠네.

  return text;
}

int main(){
  int i;
  char* p = read_line();

  for(i = 0; p[i] != 0; i++){
    if(islower(p[i]))
      p[i] = toupper(p[i]); 
    putchar(p[i]); // 조건에 상관없이 p[i]출력.
  }

  printf("\n");

  return 0;
}

read_line 함수

  1. 정적 배열 text
    • static char text[MAXCHAR]는 정적 변수로 선언되었으므로 함수가 종료되더라도 메모리에서 사라지지 않습니다.
    • 함수 호출이 끝난 뒤에도 배열 데이터를 유지해야 할 때 사용됩니다.
    • 반환 값으로 text의 시작 주소를 안전하게 반환할 수 있습니다.
  2. 문자 입력 받기
    • for 루프에서 getchar()를 사용해 한 문자씩 입력을 읽습니다.
    • 입력받은 문자를 배열 text의 각 위치에 저장합니다.
    • 입력이 줄바꿈 문자('\n')를 만나면 루프를 종료합니다.
  3. 널 문자 추가
    • 루프가 끝나면 text[i] = '\0';을 통해 문자열의 끝을 나타내는 널 문자(\0)를 추가합니다.
  4. 문자열의 시작 주소 반환
    • 배열 text의 시작 주소를 반환합니다.
    • 반환된 주소를 통해 main 함수에서 입력 문자열에 접근할 수 있습니다.

main 함수

  1. 문자열 읽기
    • read_line 함수를 호출하여 문자열을 입력받습니다.
    • 반환된 문자열의 시작 주소를 포인터 p에 저장합니다.
  2. for 루프: 문자열 처리
    • for(i = 0; p[i] != 0; i++):
      • p[i]가 널 문자(\0)가 아닐 때까지 루프가 반복됩니다.
      • 문자열의 각 문자를 처리합니다.
  3. 소문자 변환
    • islower(p[i]): p[i]가 소문자인지 확인합니다(C 라이브러리 함수).
    • toupper(p[i]): 소문자를 대문자로 변환합니다(C 라이브러리 함수).
    • 대문자로 변환된 값을 p[i]에 다시 저장합니다.
  4. 문자 출력
    • putchar(p[i]): 현재 문자를 출력합니다.
    • 변환된 대문자를 포함한 문자열이 차례대로 출력됩니다.
  5. 출력 종료
    • 루프가 끝난 후 printf("\n");로 줄바꿈을 추가합니다.

메모리 및 동작 관련 주요 포인트

  1. 정적 배열의 사용
    • text가 정적 변수(static)로 선언되었으므로 함수 종료 후에도 값을 유지합니다.
    • 정적 변수의 수명을 프로그램 종료 시점까지 연장할 수 있으므로, 로컬 배열의 주소를 반환하는 것이 안전합니다.
  2. 문자열 변환
    • 이 코드에서는 직접 입력받은 문자열을 수정하여 변환된 값을 저장합니다.
    • 배열 text는 정적으로 선언되었으므로 안전하게 수정 가능합니다.

백트래킹

#include <string.h>

void swap(char*, char*);
void permute(char*, int, int);

int main() {
  char str[] = "abc";
  printf("%d\n", strlen(str));
  permute(str, 0, strlen(str)-1); // 1

  return 0;
}

void swap(char* x, char* y){
  char temp;
  temp = *x;
  *x = *y;
  *y = temp;
}

void permute(char* s, int left, int right){ // 2
  int i;
  if(left == right) //3
    printf("%s\n", s);
  else{
    for(i = left; i<=right; i++){ //4
      swap(s+left, s+i); //5
      permute(s, left, right); // 6 -> 재귀로 순열 구함
      swap(s+left, s+i); // 7 백트래킹
    }
  }
}

문자열 입출력

int main() {
  char str[128];

  printf("Enter: \n");
  scanf("%[A-Z]s", str); //대문자 A-Z 사이 문자만 읽음

  printf("String before lower case: %s\n", str);

  while(getchar() != '\n');
  printf("Enter: \n");
  scanf("%[^e]s",str); // 첫 e를 만나기 전까지 읽음
  printf("String before e is %s\n", str);

  while(getchar() != '\n');
  printf("Enter with space: \n");
  scanf("%[^\n]s",str); // \n만나기 전까지 읽음 이게 빈칸이 아님.
  printf("you entered %s\n", str);

  while(getchar() != '\n');
  printf("Enter with space: \n");
  gets(str); // 한줄 읽기 함수. Enter누르기 전까지 모든 문자 읽음.
  printf("you entered %s\n", str);
  return 0;
}

image.png

문자열 예제

int main() {
  char str[10];
    printf("Enter a string: ");
    printf("\n");
  gets(str);
  puts(str);

  do{
    printf("Enter another string: \n");
    gets(str);
    puts(str);
  }while(*str != 0);

  printf("Enter a very Long string.\n");
  gets(str);
  printf("you entered, \n");
  puts(str);

  return 0;
}

image.png

#include <string.h>

int main() {
 char text[10];

 printf("Enter a text \n");
 fgets(text, sizeof(text), stdin);
 printf("you entered text: %s", text);
 printf(" It's length is %d\n", strlen(text)); 
 //apple 입력시 length가 5여야 되는데 6임 null때매

 text[strlen(text) - 1] = '\0';
 printf("you entered text: %s\n", text);
 printf(" It's length is %d\n", strlen(text)); 
 //이렇게 해야 5가 나온다

 return 0;
}

스크린샷 2024-12-06 오후 6.13.38.png

int main() {
  char first[6], last[6];

  printf("Enter first name.\n");
  fgets(first, sizeof(first), stdin);

  //while(getchar() != '\n');
  printf("Enter last name.\n");
  fgets(last, sizeof(last), stdin);

  printf("Full name: \n");
  puts(first);
  puts(last);

  return 0;
}

image.png

문자열 처리 함수

string.h ( strlen, strcpy, strcat, strcmp)

1. strlen
int main() {
  char *str1 = "pine", *str2 = "apple";

  if(strlen(str1) - strlen(str2) >= 0) //unsigned int기 때문에 절댓값
    printf("yes\n"); 
  else
    printf("no\n");

  if(strlen(str1) > strlen(str2))
    printf("yes\n");
  else
    printf("no\n");

  if(((int)strlen(str1) - (int)strlen(str2)) >= 0)
    printf("yes\n");
  else
    printf("no\n");

  return 0;
}
출력결과: yes, no, no
2. strcat

스크린샷 2024-12-06 오후 6.39.47.png

문자열을 이어 붙이는 함수.

src가 가리키는 문자열을 dest에 이어 붙인 다음에 dest 리턴.

3. strcmp

스크린샷 2024-12-06 오후 6.40.49.png

스크린샷 2024-12-06 오후 6.45.02.png

  • str1 > str2 → 양수 리턴
  • str1 < str2 → 음수 리턴
  • str1 == str2 → 0리턴
4. strcpy

image.png

strlen, strcat, strcpy, strcmp 예제

#include <string.h>

int main() {
  char str1[30], str2[10];

  printf("Enter a string: \n");
  gets(str1);
  printf("Enter a string: \n");
  gets(str2);

  printf("strlen(str1) = %d\n", strlen(str1));
  printf("strlen(str2) = %d\n", strlen(str2));

  if(strcmp(str1,str2) ==0)
    printf("%s and %s are equal\n", str1, str2);
  else if(strcmp(str1,str2) < 0)
    printf("%s is smaller than %s\n", str1, str2);
  else
    printf("%s is bigger than %s\n", str1, str2);

  printf("Before strcpy\n str1 = %s, str2 = %s\n", str1, str2);
  strcpy(str1, str2);
  printf("After strcpy\n str1 = %s, str2 = %s\n", str1, str2);

  printf("Before strcat\n str1 = %s, str2 = %s\n", str1, str2);
  strcpy(str1, str2);
  printf("After strcat\n str1 = %s, str2 = %s\n", str1, str2);

  printf("%s \n", strcat(str1, "OMG!"));

  return 0;
}
실행결과:
Enter a string: 
lemon
Enter a string: 
tree
strlen(str1) = 5
strlen(str2) = 4
lemon is smaller than tree
Before strcpy
 str1 = lemon, str2 = tree
After strcpy
 str1 = tree, str2 = tree
Before strcat
 str1 = tree, str2 = tree
After strcat
 str1 = tree, str2 = tree
treeOMG! 
//직접 구현한 strlen() 함수

int my_strlen(const char *str){
  int i;
  for(i=0; str[i] != '\0'; i++);
  return i;
}

int main(){
  char text[30];
  printf("Enter a text \n");
  gets(text);

  printf("Length of text is %d \n", my_strlen(text));

  return 0;
}
//재귀 호출로 strlen() 구현

int recursive_strlen(const char *str){
  if(*str == '\0')
    return 0;
  else
    return (1+recursive_strlen(++str));
}

int main(){
  char text[30];
  printf("Enter a string: \n");
  gets(text);
  printf("Lent: %d\n",recursive_strlen(text));
  return 0;
}
//직접 구현한 strcpy() 함수

char* my_strcpy(char *dest, char *src) {
  int i = 0;
  while((dest[i] = src[i]) != '\0')
    i++;
  return dest;
}

int main(){
  char dest[30], src[10];

  printf("Enter desination string\n");
  gets(dest);
  printf("Enter source string\n");
  gets(src);

  printf("On strcpy(dest, src), dest became %s\n", my_strcpy(dest, src));

  return 0;
}

src에서 dest로 복사하므로 src 앞에만 const 지정자를 붙임.

// strcpy() 포인터 버전

char* mystrcpy(char *dest, const char *src){
  char* backup = dest;
  while(*src != '\0'){
    *dest = *src;
    dest++;
    src++;
  }
  *dest = '\0';
  return backup;
}

int main(){
  char dest[30], src[10];

  printf("Enter a string: \n");
  gets(dest);
  printf("Enter another string: \n");
  gets(src);

  printf("strcpy(dest, src) -> %s \n", mystrcpy(dest, src));

  return 0;
}
// strchr() , strstr()

스크린샷 2024-12-06 오후 7.36.51.png

예제

int main() {
  char *here, *there;
  char text[] = "This is first. This is second. This is third.";
  const char ch='.';

  here = strchr(text, ch); //text문자열에서 처음 나타나는 마침표를 가리킨다.
  printf("Text after the first period is, %s\n", (here+2));
  //마침표 뒤에 빈칸이 있으므로 here+2는 다음 문장의 첫 글자.

  strcpy(text, "It is a right answer."); 
  there = strstr(text, "right"); //right의 첫번째 문자열 r
  strncpy(there, "wrong", 5); //n개의 문자만 strcpy하라. 
  //즉 there가 가리키는 위치에 wrong이라는 문자열을 복사. 문자 5개만!
  puts(text);

  return 0;
}
int main() {
  char str[100];

  char* name = "Lee eun";
  int age = 19;
  double weight = 58.5;

  char *first = "First line of a long string. ";
  char *second = "Second line of a long string. ";

  sprintf(str, "Name: %s, Age: %d, Weight: %lf.", name, age, weight);
  puts(str);

  sprintf(str, "%s %s", first, second);
  puts(str);

  return 0;
}

sprintf는 pirntf함수의 출력을 화면이 아니라 배열로 내보낸다.

#include <string.h>

int main() {
  char str[] = "J.Park Seoul 010-2222-3456";

  char *p = strtok(str, " ");
  while(p != NULL){
    printf("%s\n", p);
    p = strtok(NULL, " ");
  }

  return 0;
}
실행결과
J.Park
Seoul
010-2222-3456

strtok(string token) 함수는 문자열을 토큰 단위로 분리하기 위한 것.

strtok함수를 호출하면 처음 빈칸을 만나기 전까지의 토큰만 분리하여 그 문자열의 시작주소를 돌려준다.

스크린샷 2024-12-06 오후 8.02.47.png

유니코드 표준은 한글을 2바이트로 나타낸다.

버퍼 오버플로우 & 컴퓨터 보안

스크린샷 2024-12-06 오후 8.19.11.png

int main() {
  char str[4];
  printf("Enter a string: \n");
  scanf("%s", str);
  printf("After scanf, str became %s\n\n", str);
  return 0;
}

scanf 도 버퍼 오버플로우에 노출되어 있다. str[4]라고 선언 했으므로 ‘\0’을 제외하면 세자리만 남음.

길이가 3을 넘는 문자를 입력하면 오버플로우가 일어남.

→ 하지만 결과는 yellow를 입력하면 yellow가 나옴.

→ 이게 버퍼오버플로우가 발생한거다.

int main() {
  char str[4];

  printf("Enter a string: \n");
  scanf_s("%s", str, sizeof(str));
  printf("After scanf_s str became %s\n", str);
  return 0;
}

scanf_s를 쓰면 red를 입력시 red를 출력하지만, blue를 입력시 출력하지 않는다.

즉, 버퍼 오버플로우를 방지하기 위한 함수가 scanf_s다.

위에서 사용한 strncpy( ) 또한 버퍼 오버플로우 방지하기 위한 함수.

왜? n개 만큼만 복사하라는 뜻.

int main() {
  char dest[9];
  char* src = "mju.ac.kr";
  char str[9];

  strncpy(dest,src, sizeof(dest));
  printf("%s\n", strcpy(str,dest));
  return 0;
}

오류가 있다.

dest 길이는 9지만, src의 길이는 null까지 포함하면 10이다.

따라서 strncpy함수를 쓰면 dest에 null이 들어가지 않는다.

이 경우 문제가 발생한다.

Heap 메모리를 사용해 strcpy의 오버플로우를 방지

char* safe_strcpy(char*, const char*);

int main() {
  char source[100];
  char* destination = NULL; //문자열 복사 결과를 저장할 포인터

  printf("Enter source string: \n");
  gets(source);

  destination = safe_strcpy(destination, source);
  printf("After safe string copy, detination points to\n");
  puts(destination);
  free(destination);

  return 0;
}

char* safe_strcpy(char *dest, const char *src) {
  char* backup;
  dest = (char*)malloc(sizeof(char) * (strlen(src)+1));
  //malloc을 통해 src 문자열의 크기(strlen(src))에 맞는 메모리 할당
  // +1은 null 문자를 위한 공간임.
  if(dest == NULL){
    printf("No more memory.\n");
    exit(1);
  }
  backup = dest; // 복사된 문자열의 시작 주소를 저장
  while(*dest++ = *src++);
  return backup;
}

while문은

src에서 한 문자를 읽어 dest에 복사한 뒤 포인터를 각각 1씩 증가시킴.

null 만나면 루프 종료.

실행 과정:

  1. 입력 받기:
    • source: "Hello, World!"
  2. safe_strcpy 호출:
    • src"Hello, World!".
    • malloc으로 14바이트(13 + 1)의 메모리를 할당.
    • 문자열 "Hello, World!"를 동적 메모리로 복사.
  3. 출력:
    • "Hello, World!" 출력.
  4. 메모리 해제:
    • free(destination)로 할당된 메모리를 해제.
char* my_strcpy(char*, const char*);

int main(){
  char source[30], *destination = NULL;

  printf("Enter the string: ");
  gets(source);
  destination = my_strcpy(source, source);
  printf("After copying the string: %s\n", destination);
  puts(destination);
  free(destination);

  return 0;
}

char* my_strcpy(char* dest, const char* src){
  dest = (char*)malloc(sizeof(char) * strlen(src)+1));
  if(dest == NULL){
    printf("No more memory.\n");
    exit(1);
  }
  memset(dest, 0, sizeof(char) * (strlen(src)+1));
  memcpy(dest, src, sizeof(char) * (strlen(src)+1));
  return dest;
}

스크린샷 2024-12-06 오후 8.51.02.png

#define FRIENDS 3
#define MAX 10

#include <string.h>

void swap(char *p, char *q) {
  char temp[MAX];
  strcpy(temp,p);
  strcpy(p,q);
  strcpy(q,temp);
}

void bubble_sort(char arr[FRIENDS][MAX]) {
  int pass, current, sorted = 0;
  for(pass = 1;(pass<FRIENDS) && (!sorted) ;pass++){
    sorted = 1;
    for(current = 0; current <(FRIENDS - pass);current++){
      if(strcmp(arr[current], arr[current+1]) > 0)
        swap(arr[current], arr[current+1]);
    }
  }
}

void print_array(char arr[FRIENDS][MAX]) {
  int i;
  printf("\n");
  for(i=0; i< FRIENDS; i++)
    puts(arr[i]);
}

int main(){
  char name[MAX];
  char list[FRIENDS][MAX];
  int i;

  for(i=0; i<FRIENDS; i++){
    printf("Enter name: \n");
    gets(name);
    strcpy(list[i], name);
  }

  bubble_sort(list);
  print_array(list);

  return 0;
}

스크린샷 2024-12-06 오후 9.03.06.png

스크린샷 2024-12-06 오후 9.03.15.png

#define MAX 10
#include <stdio.h>
#include <string.h>

int main() {
    char wish_list[MAX][100];
    char temp[100];
    int i = 0, j;

    while (1) {
        printf("Enter your wishes.\n");
        gets(temp);
        if (strcmp(temp, "quit") == 0)
            break;
        strcpy(wish_list[i], temp);
        i++;
    }
    for (j = 0; j < i; j++)
        puts(wish_list[i]);

    return 0;
}
// example_12-31.c
#define MAX 10
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main() {
    char * wish_list[MAX];
    char temp[100];
    int i = 0, j;

    while (1) {
        printf("Enter your wishes.\n");
        gets(temp);
        if (strcmp(temp, "quit") == 0)
            break;
        wish_list[i] = (char*)malloc(sizeof(char)*(strlen(temp)+1));
        if (wish_list[i] == NULL) {
            fprintf(stderr, "No more memory.\n");
            exit(1);
        }
        strcpy(wish_list[i], temp);
        i++;
    }

    for (j = 0; j < i; j++)
        puts(wish_list[j]);
    for (j = 0; j < i; j++) {
        free(wish_list[j]);
        wish_list[j] = NULL;
    }

    return 0;
}

스크린샷 2024-12-06 오후 9.13.01.png

0개의 댓글