2024. 3. 27. 18:23ㆍProgramming Language/C++
아래 링크 클릭 시 해당 본문으로 이동
`strcat()`, `strcpy()`는 버퍼 오버플로우를 일으킬 가능성이 있어, 안전하지 않은 함수로 간주되어 오류가 발생한다.
그래서 좀 더 안전한 함수인 각각 `strcat_s()`, `strcpy_s()`를 사용하기를 권장한다.
strlen(), strcat_s(), strcmp(), strcpy_s()
헤더 파일
// strlen(), strcat_s(), strcmp(), strcpy_s()를 사용하려면 아래 헤더 파일을 불러와야 한다.
#include <string.h> // C 방식
#include <cstring> // C++ 방식
strlen()
• 문자열의 길이를 반환하는 함수
▶ 함수 원형
함수 원형(Function Prototype)
• 함수에 대한 정보를 컴파일러에게 미리 알리는 것
• 실제 구현이 아닌 컴파일러에 함수 이름, 반환 유형 및 매개변수를 알려주는 함수 선언
컴파일러가 함수를 정의하기 전에 함수를 호출하는 방법을 알려줄 수 있게 도와준다.
size_t strlen(const char* _String);
// strlen()이 char* 타입을 요구하는 이유
// 문자열의 시작 주소에 접근해서 배열 안에 저장되어 있는 문자가 몇 개인지 체크하고 알려주는 함수이다.
// 문자열은 배열의 초기화로써 사용되어야 하기 때문에 읽기 전용 메모리(ROM)에 존재한다.
// 배열의 요소가 char_t이고, 배열의 이름이 곧 시작 주소이기 때문에 시작 주소는 char* 타입이다.
// strlen()이 const 포인터 타입을 요구하는 이유
// 문자열의 주소를 전달하면 혹여나 값이 변경될 수도 있다는 불안 요소가 존재한다.
// 이 함수는 문자열의 길이를 알려줄 뿐, 값을 수정할 의도가 없기 때문에 앞에 const가 붙는다.
▷ 예시
#include <cstring>
int main()
{
// char 배열
char cName1[10] = "Alice";
int iLen1 = strlen(cName1); // 5
char cName2[10] = "Al\0ice";
iLen2 = strlen(cName2); // 5
// **배열의 크기 ≠ 문자열의 길이**
// const char*
const char* cName3 = "Alice";
iLen3 = strlen(cName3); // 5
return 0;
}
`strlen()` 직접 구현해 보기
// 문자열의 주소값을 변경하지 않는다는 의미로 _nL 앞에 const를 붙인다.
unsigned int myStrlen(const char* _nL)
{
int i = 0;
while (true)
{
// *(_nL + i)에서 i가 1일 때, 주소가 한 단계 증가하면 char형이므로 실제로는 1byte가 더해진다.
char c = *(_nL + i); // char c = _nL[i];
// 칸마다 접근하면서(한 칸, 두 칸, 세 칸, ⋯) 접근한 칸이 NULL이면 반복문을 빠져나오게 한다.
if ('\0' == c)
{
break;
}
++i;
}
return i;
}
int main()
{
char cName[10] = "Alice"; // myStrlen()의 _nL[]와 main()의 cName[]은 "Alice"에 접근한다.
int iLen = myStrlen(cName);
return 0;
}
※ 비교 연산자 사용할 때 `rvalue == 변수` 형태로 코드 작성하는 습관 들이기
`c = '\0'`같은 형태로 작성하면 비교 연산자를 대입 연산자로 잘못 작성했을 때 오류가 발생하지 않아서 실수인지 모르고 넘어가는 일이 발생할 수 있다.
for문이 아닌 while문을 사용한 이유
unsigned int myStrlen(const char* _nL)
{
int i = 0;
while (true)
{
// 조건식이 참이므로 무조건 실행
}
}
증감식으로 얼마를 증가시킬 건지, 무슨 연산자를 사용할 것인지와 같이 미리 정할 수 없는 부분이 있다.
반복문의 경우 while문 안에 조건식을 적지 않고 true를 사용하면 조건식이 항상 참이기 때문에 무조건 반복문이 실행된다.
덕분에 복잡한 부분을 배제한 채로 쉽게 반복문의 틀을 잡아나갈 수 있다.
▷ while문 수정
unsigned int myStrlen(const char* _nL)
{
int i = 0;
// NULL이면 반복문을 탈출하고, 접근한 칸이 NULL이 아니면 i를 증가시켜서 다음 칸으로 접근한다.
while ('\0' != _nL[i])
{
++i;
}
return i;
}
가독성이나 이해하기 쉬운 건 과정을 풀어서 작성한 코드인데 어차피 같은 의미이기 때문에 본인이 편한 방법을 선택하면 된다.
strcat()_s
• 문자열을 이어 붙여주는 함수이다.
• `strcat()_s`은 오버로딩된 함수로, 2종류가 있다.
함수 오버로딩 : 같은 함수명임에도 매개 변수(파라미터)의 자료형이 다르거나 자료형은 같아도 개수가 다른 함수를 만들 수 있는 기능
함수 오버로딩 시 컴파일러가 어떤 함수인지 구별을 할 수 있기 때문에 오류가 발생하지 않는다.
(※ 클래스에서 함수 오버라이딩과 전혀 다르지만 이름 때문에 헷갈리는 경우가 있으니 주의하기)
▶ 함수 원형
// 1. 인자를 3개 받는 경우
// strcat_s() 원형
errno_t strcat_s(char* _Destination, rsize_t _SizeInBytes, const char* _Source);
// strcat()과 달리 추가로 버퍼 크기를 매개변수로 받아 오버플로우를 방지한다.
// 배열 크기를 초과하면 오류를 반환한다.
// 2. 인자를 2개 받는 경우
// 템플릿 기반 strcat_s() 원형
errno_t strcat_s<_Size>(char (&_Destination)[_Size], const char* _Source);
1) 인자를 3개 받는 경우
함수 `(`에 커서를 놓고 단축키 <Ctrl> + <Shift> + <Space>를 누르면 함수를 사용하기 위해 필요한 요소들을 보여주는 창이 뜬다.
• 크기가 얼마인 목적지 공간에 어떤 문자열을 이어 붙인다.
Destination : 목적지
Source : 소스, 원본
① `char*_Destination` : 어디에 (목적지, 이어 붙임을 당하는 쪽)
• 다른 문자나 문자열을 붙이려면 이어 붙여지는 쪽은 수정 가능해야 한다.
② `rsize_t _SizeInWords` : 배열의 최대 개수(이어 붙이려는 목적지 공간의 크기)
• 배열의 최대 개수를 받는 이유 : 이어 붙일 쪽 배열의 최대 개수를 초과하면 안 되기 때문에 이를 방지하기 위해 복사받을 곳의 최대 공간을 입력받아서 확인해 준다.
배열 초과 시 예외 처리가 발생하므로 이어 붙여지는 쪽을 생각해서 공간 자체가 넉넉하게 잡아야 한다.
#include <cstring>
int main()
{
char cString[10] = "abc";
strcat_s(cString, 10, "defghijklmno"); // 배열 초과
// abc 뒤에 NULL까지 생각해서 배열의 공간을 설정해야 한다.
return 0;
}
배열의 최대 개수를 잘못 적으면 예외 처리나 오류가 발생하지 않고 경고만 발생한다.
시작 주소만 받아가서 주소로부터 몇 칸까지 괜찮은지 알 방법이 없기 때문에 컴파일러는 작성한 칸까지 가능하다고 믿고 그대로 실행한다.
이때 문법적으로는 오류가 발생하지 않으므로 주의해야 한다.
#include <cstring>
int main()
{
char cString[10] = "abc";
strcat_s(cString, 100, "defghijklmno"); // 배열 초과
// abc 뒤에 NULL까지 생각해서 배열의 공간을 설정해야 한다.
return 0;
}
③ `const char*_Source` : 무엇을 (이어 붙일 것)
• 이어 붙일 쪽은 원본을 수정하지 않고 읽기만 해야 되므로 const가 붙는다.
▷ 이어 붙여지는 쪽의 공간을 여유 있게 잡아놓은 경우
char cString[100] = "abc";
strcat_s(cString, 100, "def"); // abc 뒤에 def를 붙인다.
`abc` 뒤에 나머지는 전부 0으로 채워져 있지만 0은 문자로 채워진 것이 아니라 실제로는 빈 공간이므로 `abc` 바로 뒤에 `def`가 붙는다.
2) 인자를 2개 받는 경우
배열의 크기를 템플릿으로 받아가서 초과하는지 아닌지 자동으로 인식해 주는 방법이다.
직접 배열의 크기를 적는 것보다 알아서 배열의 개수가 초과되는지 체크해 주는 방법이 실수를 줄여줘서 더 좋다.
▷ 예시
#include <cstring>
int main()
{
char cString[10] = "abc";
// 템플릿 기반 strcat_s() 호출
strcat_s(cString, "def");
return 0;
}
`strcat_s()` 직접 구현해 보기
// 예외 처리가 발생하면 뜨는 창처럼 배열의 최대 개수를 초과했을 때 경고를 발생시키는 기능이다.
// 이는 특정 함수를 호출하도록 되어있는 매크로이다.
#include <assert.h> // 경고 발생시키기
#include <iostream>
using namespace std;
unsigned int myStrlen(const char* _nL)
{
int i = 0;
while (true)
{
char c = *(_nL + i); // char c = _nL[i];
if ('\0' == c)
{
break;
}
++i;
}
return i;
}
// 이어 붙인 최종 문자열의 길이가 목적지의 저장 공간을 넘어서면 예외 처리가 뜨게 해야 된다.
void myStrcat_s(char* _pDest, unsigned int _iBufferSize, const char* _pSrc)
{
int iDestLen = myStrlen(_pDest);
int iSrcLen = myStrlen(_pSrc);
// 예외 처리 => 이어 붙인 최종 문자열의 길이가 목적지 저장 공간을 넘어서려는 경우
if (_iBufferSize < iDestLen + iSrcLen + 1) // Null 문자 공간까지 계산
{
assert(nullptr);
}
// 문자열 이어 붙이기
// 1. Dest 문자열의 끝을 확인 (문자열이 이어 붙은 시작 위치)
// abc 뒤에 NULL 문자('\0') 자리부터 문자를 붙여야 되므로 목적지 문자열의 끝을 확인해야 한다.
//iDestLen; // Dest 문자열의 끝 인덱스
// 2. 반복적으로 Src 문자열을 Dest 끝 위치에 복사하기
// 3. Src 문자열의 끝을 만나면 반복 종료
// 반복문을 진행하면서 Src 문자열을 Dest 끝 위치에 복사하고, 반복 횟수는 _Source 문자열의 개수만큼이다.
for (int i = 0; i < iSrcLen + 1; ++i)
{
_pDest[iDestLen + i] = _pSrc[i];
}
}
int main()
{
char cString[10] = "abc";
myStrcat_s(cString, 10, "def");
cout << cString;
return 0;
}
▽ 출력 결과
abcdef
strcmp()
• 두 문자열을 비교하여 값을 반환하는 함수이다.
왼쪽의 우선순위가 높으면 -1을, 두 문자열이 일치하면 0을, 오른쪽의 우선순위가 높으면 1을 반환한다.
마치 저울처럼 -1, 0, 1 중에서 각각 상황에 맞게 값을 반환한다.
우선순위가 높고 낮음은 사전과 아스키 코드를 생각해 보면 된다.
더 먼저 오는 것이 우선순위가 높다.
ex. `가` > `가나`, `a` > `b`
▶ 함수 원형
int strcmp(const char* _Str1, const char* _Str2)
▷ 예시
#include <cstring>
int main()
{
// 1. 두 문자열이 일치할 때
int iRet = strcmp("abc", "abc"); // 0
// 2. 두 문자열의 길이가 다를 때
// 길이가 짧은 쪽이 우선순위가 더 높다.
iRet = strcmp("ab", "abc"); // -1
iRet = strcmp("abc", "ab"); // 1
// 3. 두 문자열의 길이가 같을 때
// 아스키 코드를 보면 a가 b보다 우선순위가 높다.
iRet = strcmp("abc", "cbc"); // -1
iRet = strcmp("cbc", "abc"); // 1
return 0;
}
'strcmp()` 직접 구현해보기
#include <assert.h>
unsigned int myStrlen(const char* _nL)
{
int i = 0;
while (true)
{
char c = *(_nL + i); // char c = _nL[i];
if ('\0' == c)
{
break;
}
++i;
}
return i;
}
int myStrcmp(const char* _left, const char* _right)
{
int leftLen = myStrlen(_left);
int rightLen = myStrlen(_right);
// 두 문자열 중 하나를 저장해놓는다.
int iLoop = leftLen; // 두 문자열이 같을 때 아래 if문을 실행시킬 필요가 없어진다.
int iReturn = 0;
// 두 문자열의 길이가 다를 때
// 왼쪽 문자열이 더 작으면 -1을 반환한다.
if(leftLen < rightLen)
{
iLoop = leftLen;
iReturn = -1;
}
// 오른쪽 문자열이 더 작으면 -1을 반환한다.
else if(leftLen > rightLen)
{
iLoop = rightLen;
iReturn = 1;
}
// 반복문은 두 문자의 반복 횟수에 맞게 반복된다.
for (int i = 0; i < iLoop; ++i)
{
// 왼쪽 문자가 오른쪽보다 작다면 -1을, 그렇지 않다면 1을 반환한다.
if (_left[i] < _right[i])
{
return -1;
}
else if(_left[i] > _right[i]) // ※ else로 작성하면 작다를 제외한 크거나 "같은" 경우도 포함되어버린다.
{
return 1;
}
}
return iReturn; // -1, 0, 1 중에서 해당하는 것을 반환한다.
}
int main()
{
int iRet = myStrcmp("abc", "ab"); // 1
iRet = myStrcmp("abc", "abcdef"); // -1
return 0;
}
strcpy_s()
• 문자열을 복사하는 함수
▶ 함수 원형
errno_t strcpy_s<_Size>(char* (&_Destination)[_Size], const char* _Source)
▷ 예시
#include <cstring>
#include <iostream>
using namespace std;
int main()
{
char c[] = "Hello World!";
char dest[30];
strcpy_s(dest, c);
cout << dest;
return 0;
}
▽ 출력 결과
Hello World!
`strcpy_s()` 직접 구현해 보기
#include <iostream>
void myStrcpy_s(char* _pDest, const char* _pSrc)
{
int i = 0;
// _pSrc 문자열을 끝까지 순차적으로 복사한다.
while (_pSrc[i] != '\0')
{
_pDest[i] = _pSrc[i];
++i;
}
// 마지막에 NULL 문자 추가
_pDest[i] = '\0';
}
int main()
{
char cSrc[] = "Hello";
char cDest[50]; // 복사본을 저장할 배열
cout << myStrcpy_s(cDest, cSrc);
return 0;
}
▽ 출력 결과
Hello
'Programming Language > C++' 카테고리의 다른 글
[C/C++] 가변 배열 - 구조체/분할 구현 (0) | 2024.03.28 |
---|---|
[C++] wchar_t 배열 함수 - wcslen(), wcscat_s(), wcscmp(), wcscpy_s() (0) | 2024.03.27 |
[C++] 문자열 (0) | 2024.03.26 |
[C++] 스트림(stream), 버퍼(buffer) (0) | 2024.03.26 |
[C++] 주석(Comment), 세미콜론(;) (0) | 2024.03.25 |