[C++] 문자열 (string, string_view)

chooha·2026년 1월 17일

C++

목록 보기
21/22

📚 C++ 문자열 (string, string_view)

🔸 C-Style 문자열의 메모리 위치

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';  // 컴파일 에러로 실수 방지!

🔸 std::string의 메모리 할당

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 (C++17)

std::string_view란?

  • std::span과 비슷한 개념
  • 포인터 + 길이 정보만 저장 (16 bytes)
  • 소유권 없이 문자열을 참조만 함

메모리 구조

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배 이상 성능 향상 가능

🔸 std::string_view 주의사항

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*, std::string 비교

타입메모리 위치수정 가능크기 정보메모리 관리
char[]스택컴파일 타임자동
const char*리터럴: read-only
동적: 힙
리터럴: ❌
동적: ✅
❌ (strlen 필요)리터럴: 자동
동적: 수동
std::stringSSO: 스택
긴 문자열: 힙
✅ (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의 메모리

  • 짧은 문자열 (15-22자): 스택 (SSO)
  • 긴 문자열: 힙 동적 할당
  • 자동 메모리 관리 (RAII)

std::string_view

  • 포인터 + 길이만 저장 (16 bytes)
  • const char*, std::string 모두 임시 객체 없이 전달
  • 읽기 전용, 소유권 없음
  • 주의: 원본 수명 관리 필요

함수 파라미터 권장사항 (C++17 이상)

  • 읽기 전용: std::string_view
  • 수정 필요: std::string&
  • 소유권 획득: std::string (값 전달)

<참고 자료>

코드없는 프로그래밍
C++ std::string Implementation
C++ std::string_view

0개의 댓글