char 배열 vs char 포인터
int main()
{
char a[] = "hello"; // 스택에 할당
char* b = "hello"; // 포인터는 스택, 문자열은 read-only 영역
// 수정 테스트
a[0] = 'd'; // ✅ 성공: "dello"
// b[0] = 'd'; // ❌ Segmentation Fault!
return 0;
}
메모리 레이아웃
스택 (Stack):
┌──────────────┐
│ a[] 배열 │
│ 'h''e''l''l' │
│ 'o' '\0' │
└──────────────┘
↑ 수정 가능
스택 (Stack):
┌──────────────┐
│ b (포인터) │
│ 0x400000 ────┼───→ Read-Only Data Segment:
└──────────────┘ ┌──────────────────┐
│ "hello\0" │
└──────────────────┘
↑ 수정 불가능!
문자열 리터럴은 read-only data segment에 저장되며, 수정 시도 시 segmentation fault 발생
const char*에는 반드시 const 사용
// ❌ 나쁜 코드: 경고 발생 (C++11 이후 deprecated)
char* b = "hello";
b[0] = 'd'; // Undefined Behavior (대부분 Segmentation Fault)
// ✅ 좋은 코드: const로 명시
const char* b = "hello"; // 수정 불가능함을 명시
// b[0] = 'd'; // 컴파일 에러로 실수 방지!
Small String Optimization (SSO)
std::string은 항상 힙에 할당되는 것이 아니라, SSO를 통해 짧은 문자열은 스택에, 긴 문자열만 힙에 저장
#include <string>
int main()
{
// 짧은 문자열 (SSO 적용, 보통 15-22자 이하)
std::string short_str = "hello";
// → 스택에 저장 (힙 할당 없음)
// 긴 문자열 (SSO 한계 초과)
std::string long_str = "This is a very long string that exceeds SSO";
// → 힙에 동적 할당
return 0;
}
SSO 동작 방식
std::string 객체 (스택):
┌────────────────────────┐
│ 포인터 / 데이터 │ ← SSO 버퍼 (15-22자)
│ 길이 │
│ 용량 / SSO 플래그 │
└────────────────────────┘
짧은 문자열 ("hello"):
┌─────────────────────────┐
│ "hello\0"... (여유공간)│ ← 스택 내부에 직접 저장
│ 길이: 5 │
│ 플래그: SSO 사용 중 │
└─────────────────────────┘
긴 문자열:
┌───────────────┐
│ 포인터 ───────┼───→ 힙 메모리
│ 길이: 50 │ "This is a very long..."
│ 용량: 64 │
└───────────────┘
구현에 따라 GCC/MSVC는 15자, Clang은 22자까지 SSO 적용
std::string_view란?
std::span과 비슷한 개념메모리 구조
template<typename CharT>
class basic_string_view
{
const CharT* ptr; // 데이터 시작 주소 (8 bytes)
size_t length; // 문자열 길이 (8 bytes)
};
// sizeof(std::string_view) == 16 bytes
const std::string& 사용 시 문제
void print(const std::string& str)
{
std::cout << str << std::endl;
}
int main()
{
const char* c_str = "hello";
// ❌ 비효율: 임시 std::string 객체 생성!
print(c_str);
// 실제 동작:
// 1. 임시 std::string tmp(c_str) 생성
// 2. 힙 메모리 할당 (SSO 초과 시)
// 3. "hello" 복사
// 4. print(tmp) 호출
// 5. tmp 소멸, 메모리 해제
return 0;
}
const char*나 문자열 리터럴을 const std::string& 파라미터로 전달하면 임시 std::string 객체가 생성되어 메모리 할당과 복사 발생
std::string_view 사용
void print(std::string_view str) // ✅ 효율적!
{
std::cout << str << std::endl;
}
int main()
{
// 모두 임시 객체 생성 없이 전달 가능
// C 문자열 배열
char arr[] = "hello";
print(arr); // 포인터 + 길이만 전달
// const char* 포인터
const char* c_str = "world";
print(c_str); // 포인터 + 길이만 전달
// std::string
std::string cpp_str = "string";
print(cpp_str); // string의 내부 포인터 + 길이만 전달
return 0;
}
string_view는 포인터와 길이만 저장하므로 임시 객체 생성이나 메모리 할당 없이 전달 가능
성능 차이
// const std::string& 사용
void old_print(const std::string& str) { }
old_print("hello");
// → 임시 std::string 생성
// → 힙 할당 (SSO 초과 시)
// → 메모리 복사
// → 함수 호출
// → 소멸 및 메모리 해제
// std::string_view 사용
void new_print(std::string_view str) { }
new_print("hello");
// → 포인터와 길이만 전달 (16 bytes 복사)
// → 끝!
벤치마크 결과 string_view 사용 시 문자열 리터럴 전달에서 100배 이상 성능 향상 가능
Dangling Reference
std::string_view get_view()
{
std::string temp = "temporary";
return std::string_view(temp); // ❌ 위험!
} // temp 소멸
int main()
{
auto view = get_view();
std::cout << view << std::endl; // ❌ Undefined Behavior
return 0;
}
std::string 재할당 시 무효화
std::string str = "short";
std::string_view view = str;
std::cout << view << std::endl; // ✅ "short"
str = "This is a very long string that causes reallocation";
// → str의 내부 버퍼가 재할당됨
std::cout << view << std::endl; // ❌ Undefined Behavior
// view는 여전히 옛날 주소를 가리킴
임시 객체 바인딩
void process(std::string_view view)
{
// view 사용...
}
int main()
{
std::string get_string() { return "temp"; }
// ❌ 위험: 임시 객체의 view
process(get_string()); // get_string() 반환 후 즉시 소멸
// ✅ 안전: 수명 연장
std::string str = get_string();
process(str);
return 0;
}
string_view는 임시 객체도 받아들이므로, 수명 관리에 주의 필요
| 타입 | 메모리 위치 | 수정 가능 | 크기 정보 | 메모리 관리 |
|---|---|---|---|---|
char[] | 스택 | ✅ | 컴파일 타임 | 자동 |
const char* | 리터럴: read-only 동적: 힙 | 리터럴: ❌ 동적: ✅ | ❌ (strlen 필요) | 리터럴: 자동 동적: 수동 |
std::string | SSO: 스택 긴 문자열: 힙 | ✅ | ✅ (O(1)) | 자동 (RAII) |
std::string_view | 원본 참조 | ❌ (읽기 전용) | ✅ (O(1)) | 소유권 없음 |
// 읽기 전용, 다양한 타입 받기
void read_only(std::string_view str) // ✅ 권장 (C++17 이상)
{
std::cout << str << std::endl;
}
// std::string만 받기
void string_only(const std::string& str)
{
std::cout << str.size() << std::endl;
}
// 소유권 획득
void take_ownership(std::string str) // 복사 또는 이동
{
// str을 멤버 변수에 저장하거나 수정
}
// 수정이 필요한 경우
void modify(std::string& str)
{
str += " modified";
}
// C 라이브러리와 호환 필요
void c_compat(const char* str)
{
printf("%s\n", str); // C 함수 사용
}
char 배열 vs char 포인터
char a[] = "hello": 스택에 복사본 생성, 수정 가능const char* b = "hello": read-only 영역 참조, 수정 불가 (const 필수)std::string의 메모리
std::string_view
const char*, std::string 모두 임시 객체 없이 전달함수 파라미터 권장사항 (C++17 이상)
std::string_viewstd::string&std::string (값 전달)코드없는 프로그래밍
C++ std::string Implementation
C++ std::string_view