Notice
Recent Posts
Recent Comments
Link
«   2026/01   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

우당탕탕 개발일지

[C언어] 문자열 (feat. strcpy, strtok_r 활용 예제) 본문

Server/Linux, C

[C언어] 문자열 (feat. strcpy, strtok_r 활용 예제)

YUDENG 2025. 5. 11. 14:38

1. strcpy(), strncpy()

 

반환값

  • strcpy() 함수와 strncpy() 함수는 dest에 대한 포인터를 반환한다.

 

strcpy 형식

#include <string.h>
char *strcpy(char *dest, const char *src);
  • src 문자열을 null('\0')을 포함하여 dest로 복사한다.
  • 2번째 문자열 src의 크기가 1번째 문자열 dest 보다 크다면 오버플로우가 발생한다.
    → 대상 문자열 dest는 복사본을 수신할 만큼 충분히 커야 한다.

strncpy 형식

#include <string.h>
char *strncpy(char *dest, const char *src, size_t n);
  • strcpy() 함수와 비슷하지만 src 의 최대 n 바이트만 복사되기 때문에 오버플로우를 방지할 수 있다.
  • src 크기가 n보다 작다면, 나머지 dest의 값은 null('\0') 로 채워진다.
  • strncpy 함수는 dest에 추가 null 바이트를 써서 총 n 바이트가 쓰이도록 한다.

대상 버퍼 크기가 충분하지 않으면 null('\0)이 복사되지 않아 끝나지 않는 문자열을 만들 수 있기에 주의해야 한다.

 

 

🔷 strcpy()의 개선된 버전인 my_strcpy() 함수

 

함수 프로토타입: int my_strcpy(char *dest, size_t dest_buf_size, const char *src)

  • src의 문자열을 dest로 복사하는 함수로, dest 버퍼의 길이는 dest_buf_size로 src 포인터가 가리키는 문자열 배열에서 src 문자열 길이만큼 dest로 복사를 수행하는데 src 문자열 길이가 dest_buf_size보다 큰 경우에는 dest_buf_size-1 만큼만 복사하고 마지막에 null 문자를 저장
#include <stdio.h>
#include <string.h>

int my_strcpy(char *dest, size_t dest_buf_size, const char *src) {

    if (dest == NULL) {
        printf("my_strcpy : dest is null\n");
        return -1;
    }

    if (dest_buf_size <= 0) {
        printf("my_strcpy : dest_buf_size is too small\n");
        return -1;
    }

    if (src == NULL) {
        printf("my_strcpy : src is null\n");
        return -1;
    }

    int i = 0;
    while(i < dest_buf_size - 1 && src[i] != '\0') {
      dest[i] = src[i];
      i++;
    }

    dest[i] = '\0';
    return i;
}

2. strlen()

 

반환값

  • strlen() 함수는 s 문자열의 바이트 수를 반환한다.

 

strlen 형식

#include <string.h>
size_t strlen(const char *s);
  • strlen() 함수는 종료 널 바이트('\0')를 제외한 s 문자열의 길이를 계산한다.
  • null('\0') 이 없는 문자열이 들어오면 위험할 수 있다.
strlen 함수는 기본적으로 null('\0') 이 나올때까지 1씩 주소를 증가시키면서 문자를 비교해 나간다. 따라서 문자열이 null('\0') 을 포함하고 있지 않다면 문자열을 포함하는 변수의 메모리 공간을 벗어나더라도 계속해서 null('\0') 이 나올 때까지 문자의 개수를 계산하게 된다.

 


3. strcmp(), strncmp()

 

반환값

조건 반환값
str1 > str2 양수
str1 == str2 0
str1 < str2 음수

 

 

strcmp 형식

#include <string.h>
int strcmp(const char *s1, const char *s2);
  • strcmp() 함수는 두 문자열 s1과 s2를 비교한다.

strncmp 형식

#include <string.h>
int strncmp(const char *s1, const char *s2, size_t n);
  • strcmp() 함수와 비슷하지만, s1과 s2의 n 바이트만 비교한다.
  • 세번째 매개변수 n에 0을 넣은 경우에는 항상 0이 나온다.
  • -1을 넣은 경우에는 unsigned int 타입에서 언더플로우가 일어나기 때문에 최댓값이 들어가므로 매개변수로 들어온 문자열 끝까지 비교하게 된다.
  • 문자열 str1과 str2 보다 큰 값을 넣게 되면 알아서 문자열 전체를 비교한다.
각 문자를 아스키 코드로 비교하기 때문에 대소문자 구분이 가능하다.

4. strcasecmp(), strncasecmp()

 

반환값

조건 반환값
str1 > str2 양수
str1 == str2 0
str1 < str2 음수

 

 

strcasecmp 형식

#include <strings.h>
int strcasecmp(const char *s1, const char *s2);
 

 

※ s1 : 비교할 대상 문자열
※ s2 : 비교할 문자열

  • strcasecmpp() 함수는 문자의 대소문자를 무시하면서 문자열 s1과 s2를 바이트 단위로 비교한다.

strncasecmp 형식

#include <strings.h>
int strncasecmp(const char *s1, const char *s2, size_t n);
 
  • strcasecmpp() 함수와 비슷하지만, s1과 s2의 n 바이트만 비교한다.
대부분의 함수들이 -1, 0, 1을 return 하지만, manual에서는 0보다 작은값, 0, 0보다 큰 값을 return한다고 되어 있기 때문에 if(strcasecmp(s1, s2) == -1) 보다는 if(strcasecmp(s1, s2) < 0) 형식으로 표현하는 것이 호환성에 있어 좋은 방법이다.

5. strcat(), strncat()

 

반환값

  • strcat() 함수와 strncat() 함수는 dest에 대한 포인터를 반환한다.

 

strcat 형식

#include <string.h>
char *strcat(char *dest, const char *src);
  • 문자열 src를 문자열 dest에 덧붙인다. dest 끝에 있던 종료 널 바이트('\0')을 덮어 쓰고 새 종료 널 바이트('\0')를 추가한다.
  • 두 문자열이 겹쳐서는 안되며 dest 문자열의 크기는 src와 합쳐도 남을 정도로 충분히 길어야 한다.

strncat 형식

#include <string.h>
char *strncat(char *dest, const char *src, size_t n);
  • strncat() 함수는 다음을 제외하고는 strcat() 함수와 유사하다.
    • 최대 n바이트의 src를 사용한다.
    • src에 n개 이상의 바이트가 있는 경우 널 종료일 필요가 없다.
  • dest의 결과 문자열은 항상 null로 끝난다.
  • src에 n개 이상의 바이트가 있는 경우 strncat()은 n+1개 바이트(src의 n개와 종료 널 바이트)를 dest에 써넣는다. 따라서 dest의 크기가 최소 strlen(dest)+n+1이어야 한다.

 


6. strchr(), strrchr()

 

strchr 형식

#include <string.h>
char *strchr(const char *s, int c);
  • 문자열에서 특정 문자를 찾을 때 사용하는 함수이다.
  • strchr() 함수는 문자열 s에서 문자 c의 첫 번째 발생에 대한 포인터를 반환하며, 찾지 못했다면 NULL을 반환한다.

 

strrchr 형식

#include <string.h>
char *strrchr(const char *s, int c);
  • 문자열에서 특정 문자를 찾을 때 사용하는 함수로, 문자열의 뒤에서부터 검사를 한다.
  • strrchr() 함수는 문자열 s에서 문자 c의 마지막 발생에 대한 포인터를 반환하며, 찾지 못했다면 NULL을 반환한다.

7. strstr()

 

반환값

  • 하위 문자열 시작점에 대한 포인터를 반환한다. 하위 문자열을 찾지 못하면 NULL을 반환한다.

 

strstr 형식

#include <string.h>
char *strstr(const char *haystack, const char *needle);
  • 문자열 haystack에서 하위 문자열 needle의 첫 번째 위치를 찾는다.
  • 종료 널 바이트('\0')는 비교하지 않는다.

8. strtok(), strtok_r()

 

반환값

  • strtok() 함수와 strtok_r() 함수는 분리된 문자열의 시작 포인터를 반환한다.
  • delim을 찾지 못하거나 더이상 없는 경우 NULL을 반환한다.

 

strtok 형식

#include <string.h>
char *strtok(char *restrict str, const char *restrict delim);
 
  • strtok("문자열", "구분자")
  • strtok()함수는 문자열 str을 구분문자 delim 단위로 쪼개며, 이렇게 쪼개진 문자열을 토큰(token)이라고 한다.
  • delim에 포함된 문자들 중 하나를 만나는 곳이 토큰의 마지막이 되며, 원래 delimiter가 있었던 토큰의 마지막을 null-terminated 문자열로 변경하여 반환한다.
  • 처음 호출할 때만 분해할 문자열을 str로 지정해주고, 그 다음부터는 토큰의 위치를 기억하고 있기 때문에 따로 문자열을 지정하지 않고 strtok()함수를 계속 호출한다. 문자열 str 의 다음 토큰을 구하려면 NULL을 넣어주면 된다.

 

strtok 주의할 점

 

1. strtok() 함수는 원본 데이터 문자열 str 을 훼손한다.

strtok함수는 새로운 메모리를 할당하지 않기 때문에 토큰화에 쓰이는 문자열 str은 어쩔 수 없이 바뀌게 된다. (원본을 지키고 싶으면 사본을 만든 뒤 strtok() 함수를를 호출해야 한다.

 

2. strtok() 함수는 스레드 안전 하지 않은 함수이다.

strtok() 함수 내부에서는 static pointer 변수를 통해 token 이후 남아있는 문자열의 포인터를 저장하고 있다. 하지만 멀티스레드 환경인 경우, 각각의 스레드가 동시에 strtok 함수를 호출할 경우, 내부의 static pointer 변수가 덮어씌워지는 오류가 발생한다.

 

사용 예시

char str[] = "hello,world";      //구분할 문자열
char *ptr = strtok(str, " ");    //첫번째 strtok 사용.

while (ptr != NULL)              //ptr이 NULL일때까지 (= strtok 함수가 NULL을 반환할때까지)
{    
  printf("%s\n", ptr);           //자른 문자 출력
  ptr = strtok(NULL, " ");       //자른 문자 다음부터 구분자 또 찾기
}
  • 첫 번째 인자에 NULL을 넣어주는 것은 “이전에 너가 구분자를 찾았던 그 문자열 주소에서 부터 다시 찾아달라”는 뜻을 가지고 있다.

 

strtok_r 형식

#include <string.h>
char *strtok_r(char *restrict str, const char *restrict delim, char **restrict saveptr);
 
  • strtok() 함수의 재진입 가능 버전으로, 정적 버퍼를 사용하는 대신에 사용자가 제공하는 포인터를 인자로 받아 처리하기 때문에 스레드 간 간섭 없이 안전하게 동작한다.
  • saveptr 인자는 char * 변수의 포인터이다. 같은 문자열을 분해하는 연이은 호출들에서 문맥을 유지하기 위해 strtok_r() 내부에서 그 변수를 사용한다.
  • saveptr 인자를 다르게 지정한 strtok_r() 호출들을 이용해 여러 문자열을 동시에 분해할 수 있다.

 

사용 예시

char str[] = "hello,world";
char *token;
char *rest = str;

while ((token = strtok_r(rest, ",", &rest))) {
    printf("%s\n", token);
}
 

9. strtol(), strtoll()

 

반환값

  • 성공적으로 완료되면 변환된 값을 반환한다.
  • 변환을 수행할 수 없으면 0이 반환되고, errno는 [EINVAL]로 설정된다.
  • 언더플로가 일어나면 strtol()은 LONG_MIN을 반환한다. 오버플로가 일어나면 strtol()은 LONG_MAX를 반환한다. → 두 경우 errno를 [ERANGE]로 설정한다.

 

strtol 형식

#include <stdlib.h>
long strtol(const char *restrict nptr, char **restrict endptr, int base);
 

※ nptr: 변환하려고 하는 정수 문자열
※ endptr: 해석하지 못한 첫 번째 문자의 주소
※ base: 진법

  • 숫자 형식의 문자열을 숫자로 변경해주는 함수이다.
  • nptr에 있는 문자열의 처음 부분을 지정한 base에 따라 long 정수 값으로 변환한다.
  • 기수는 2와 36 사이이거나 특수한 값 0이어야 한다.

 

atoi 와의 차이점

 

1. 에러 처리 기능

  • atoi는 잘못된 입력에 대해 에러 처리를 제공하지 않는다.
  • strtol 은 변환 실패를 감지할 수 있도록 endptr와 errno를 제공한다.

2. 진법 지원

  • atoi 는 항상 10진수로 해석한다.
  • strtol 은 2진수, 8진수, 16진수 등 다양한 진법을 지원한다.

3. 범위 초과 처리

  • atoi는 범위를 벗어난 입력에 대해 정의되지 않은 동작(UB)을 일으킬 수 있다.
  • strtol 은 범위를 초과할 경우 최대/최소 값을 반환하고 errno를 설정한다.

 

strtoll 형식

#include <stdlib.h>
long long strtoll(const char *restrict nptr,
            char **restrit endptr, int base);
 
  • strtoll() 함수는 strtol() 함수처럼 동작하되 long long 정수 값을 반환한다.
  • endptr이 NULL이 아니면 strtol()은 첫 번째 비유효 문자의 주소를 *endptr에 저장한다.

 

🔷 atoi()의 개선된 버전인 my_atoi() 함수

  • int my_atoi( char *str, int *pn_result )
    • my_atoi() 함수의 실행 결과를 나타내는 리턴 값은 다음과 같이 정의 된다.
      • 0 //성공
      • -1 //인자가 잘못됨
      • -2 // str이 가리키는 문자열이 숫자가 아님
      • -3 // str이 가리키는 문자열이 int로 표현할 수 있는 범위를 넘음
  • 리눅스에서 제공하는 API를 사용하지 말고 직접 문자열을 처리해서 변환하도록 함수를 작성
  • strtol() 함수를 사용해서 변환하는 버전의 함수 작성
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <errno.h>

int my_atoi(char *str, int *pn_result) {
	
	if(str == NULL) {
		printf("my_atoi : str is null\n");
		return -1;
	}

	if(pn_result == NULL) {
		printf("my_atoi : pn_result is null\n");
		return -1;
	}
	
	char *endptr = NULL;
	errno = 0;

	long value = strtol(str, &endptr, 10);

	if(endptr == str || errno == EINVAL) {
		printf("my_atoi : str is not numbers\n");
		return -2;
	}

	if(errno == ERANGE) {
		printf("my_atoi : str is out of range\n");
		return -3;
	}

	*pn_result = (int)value;

	return 0;
}

 

 

🔷 수강 신청 정보를 입력받아 관리하는 수강 신청 프로그램 (응용)

  • 수강 신청 정보는 최대로 5개까지 저장해야 하며, 5개 초과되는 정보를 입력한 경우에는 에러를 출력한다.
  • 입력된 수강 신청 정보는 메모리에 보관하고 프로그램 재기동시에 리셋된다.
  • 프로그램은 수강 신청 입력, 수강 신청 출력, 종료 3개 메뉴를 출력하고 사용자가 종료를 선택하기 전까지 반복해서 수강 신청 입력/출력/종료를 반복한다.
  • 수강 신청 정보 입력은 my_getline() 함수를 사용한다.

 

🔹 입력

 

1. 사용자가 메뉴에 해당하는 번호를 입력한다.

  • 1은 수강 신청 정보 입력, 2는 수강 신청 정보 출력, 3은 종료
  • 메뉴에 없는 번호를 입력한 경우, 다시 메뉴를 출력한다.

2. 수강 신청 정보 입력을 선택하면 다음과 같이 출력하고 ID와 수강 신청 과목 정보를 입력받는다.

  ID:
  수강 신청 과목:

  • ID는 정수로 1 <= ID <= 100 의 범위 값을 가진다. 범위에 벗어나는 값을 입력한 경우 에러 문구 출력하고 메뉴를 출력한다.
  • 수강 신청 과목은 컴퓨터개론, 이산수학, C언어, JAVA초급, 리눅스구조, 자료구조, 컴파일러, 네트워크개론이다.
  •  수강 신청 과목: 과목1, 과목2, 과목3, …
    • 수강 신청할 과목명만 콤마(,)로 구분하여 입력
    • 콤마와 과목명 사이의 공백 문자가 몇 개가 있던 무시하고 과목명 입력이 되어야 함.
    • 존재하지 않는 과목명 입력시 에러 문구를 출력하고 메뉴를 출력

3. 수강 신청 출력을 선택하면, 다음과 같은 포맷으로 수강 신청 정보를 출력한다(ID당 한줄씩 출력)

 ID: 입력한 ID // 수강신청 과목: 신청한 과목1, 신청한 과목2, 신청한 과목3, ...

 

4. 종료를 선택하면 프로그램을 종료한다.

 

 

[C언어] 기본 입출력 및 비트 연산자

Linux man 사용법$ man [options] [section] command[SPACE] : 한 페이지 밑으로 내려간다[ENTER] : 한 줄 밑으로 내려간다. [b] : 전 페이지로 올라간다. [q] : man 명령을 종료한다. 1. printf(), sprintf(), snprintf() ✅ printf

uj0791.tistory.com

※ my_getline() 함수 참고

 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "my_getline.h"
#include "my_atoi.h"

#define MIN_ID 1
#define MAX_ID 100
#define MAX_SUBJECTS 8
#define MAX_STORE 5

typedef struct _subjects_info_s {
	int id;
	unsigned char subjects;
} subjects_info_t;

typedef struct _subjects_data_s {
	subjects_info_t subjects_info[MAX_STORE];
	size_t subjects_info_size;
} subjects_data_t;

const char *subjects[] = {
	"컴퓨터개론", "이산수학", "C언어", "JAVA초급",
	"리눅스구조", "자료구조", "컴파일러", "네트워크개론"
};

int subjects_input(subjects_data_t *subjects_data) {
	if (subjects_data == NULL) {
		printf("subjects_input : subjects_data is null\n");
		return -1;
	}

	if (subjects_data->subjects_info_size >= MAX_STORE) {
		printf("subjects_input : subjects_data is full\n");
		return -1;
	}

	printf("ID: ");
	char id_buf[4];

	if (line(id_buf, sizeof(id_buf)) == -1) {
		printf("subject_input : id input getline error\n");
		return -1;
	}

	int id;
	if(my_atoi(id_buf, &id) < 0) {
		printf("subject_input : id conversion error\n");
		return -1;
	}

	if (id < MIN_ID || id > MAX_ID) {
		printf("subject_input : id is out of range\n");
		return -1;
	}

	subjects_info_t *cur = &subjects_data->subjects_info[subjects_data->subjects_info_size];
	cur->id = id;
	cur->subjects = 0;

	printf("수강 신청 과목: ");

	char subject_buf[256];
	if (line(subject_buf, sizeof(subject_buf)) == -1) {
		printf("subjects_input : subjects input getline error\n");
		return -1;
	}

	if (set_bitmask(subject_buf, &cur->subjects) == -1) {
		printf("subjects_input : set subject bitmask error\n");
		return -1;
	}

	subjects_data->subjects_info_size++;
	return 0;
}

#define DELIM_CHARS ","

int set_bitmask(char *subject_buf, unsigned char *subject_bitmask) {

	if(subject_buf == NULL) {
		printf("set_bitmask : subject_buf is null\n");
		return -1;
	}

	if(subject_bitmask == NULL) {
		printf("set_bitmask : subject_bitmask is null\n");
		return -1;
	}

	char *rest = subject_buf;
	char *token = strtok_r(subject_buf, DELIM_CHARS, &rest);

	while(token != NULL) {
		if((remove_space(token)) != 0) {
			printf("remove token space is error\n");
			return -1;
		}

		if (token[0] == '\0') {
			token = strtok_r(NULL, DELIM_CHARS, &rest);
			continue;
		}

		int index = valid_subjects(token);
		if(index == -1) {
			printf("Is not valid subject: %s\n", token);
			return -1;
		}

		*subject_bitmask |= (1 << index);
		token = strtok_r(NULL, DELIM_CHARS, &rest);
	}

	return 0;
}

int remove_space(char *str) {

	if(str == NULL) {
		printf("remove_space : str is null\n");
		return -1;
	}

	int i, j = 0;
	int len = strlen(str);

	for(i = 0; i < len; i++) {
		if (str[i] != ' ' && str[i] != '\n') {
			str[j++] = str[i];
		}
	}

	str[j] = '\0';
	return 0;
}

int valid_subjects(char *subject) {

	if(subject == NULL) {
		printf("valid_subjects : subject is null\n");
		return -1;
	}

	for (int i = 0; i < MAX_SUBJECTS; i++) {
		if (strcmp(subject, subjects[i]) == 0)
			return i;
	}

	return -1;
}

int subjects_output(subjects_data_t *subjects_data) {

	if (subjects_data == NULL) {
		printf("subjects_output : subjects_data is null\n");
		return -1;
	}

	if (subjects_data->subjects_info_size <= 0) {
		printf("수강 신청 내역이 없습니다..\n");
		return 0;
	}

	int i, j;

	for (i = 0; i < subjects_data->subjects_info_size; i++) {
		printf("ID: %d // 수강신청 과목: ", subjects_data->subjects_info[i].id);

		int first_flag = 1;
		for (j = 0; j < MAX_SUBJECTS; j++) {
			if ((subjects_data->subjects_info[i].subjects & (1 << j)) != 0) {
				if (!first_flag) {
					printf(", ");
				} else {
					first_flag = 0;
				}
				printf("%s", subjects[j]);
			}
		}
		printf("\n");
	}

	return 0;
}

int main(void) {
	int num = 0;
	char buf[2];

	subjects_data_t subjects_data;
	memset(&subjects_data, 0x00, sizeof(subjects_data));

	while (1) {
		printf("--------------------------------------------------------- \n");
		printf("1. 수강 신청 정보 입력\n2. 수강 신청 정보 출력\n3. 종료\n");
		printf("select num: ");

		if (line(buf, 2) < 0) {
			printf("get line fail\n");
			continue;
		}

		switch (buf[0]) {
			case '1':
				if (subjects_input(&subjects_data) < 0) {
					printf("subject information input error\n");
				}
				break;

			case '2':
				subjects_output(&subjects_data);
				break;

			case '3':
				exit(0);

			default:
				break;
		}
	}

	return 0;
}
728x90

'Server > Linux, C' 카테고리의 다른 글

[C언어] Thread  (4) 2025.06.25
[C언어] Makefile / GDB  (0) 2025.05.25
[C언어] 전처리기  (0) 2025.05.25
[Linux] 리눅스 파일과 파일 시스템  (1) 2025.05.11
[C언어] 기본 입출력 및 비트 연산자  (0) 2025.05.11