문자열 리터럴이란 무엇일까?

다음은 문자열 리터럴을 설명해주는 좋은 예시이다.

std::cout << "Hello World" << std::endl;

위 문장에서 "Hello World" 가 문자열 리터럴에 해당한다.

본 질문에 앞서 다음의 코드를 살펴보자.

#include <iostream>

using namespace std;

int main()
{
  char* ptr1 = "char array literal"; // C++14 or below 
  char* ptr2 = "char array literal";
  char* ptr3 = "char array literal";
  char* ptr4 = "char array literal";

  printf("%p %p %p %p", ptr1, ptr2, ptr3, ptr4);

  return 0;
}

위 코드의 실행결과는 다음과 같다. `0x4007e4 0x4007e4 0x4007e4 0x4007e4 ` 메모리 주소의 공간은 실행시 계속적으로 변할 수 있으니 단순히 동일한 값만을 출력한다는 것만 기억하자. 그렇다면 왜 동일한 문자열 리터럴은 동일한 주소값을 가질까?
문자열 리터럴은 **ROM**공간에 저장된다. 이러한 이유는 컴파일러가 메모리를 절약하기 위함이다. 이러한 목적에 따라 위 코드에서 `ptr1`, `ptr2`, `ptr3`, `ptr4`는 같은 주소공간을 가리키게 된 것이다. 이러한 방식을 리터럴 풀링(literal pooling)이라 부른다.

그렇다면 다음의 코드는 어떤 결과를 발생할까?

#include

using namespace std;

int main()  
{  
char\* ptr = "char array literal";  
ptr\[0\] = 'L';

cout << ptr << endl;

return 0;  
}

당연하게도 위 코드는 실행되지 않고 컴파일 에러를 발생시킨다. ptr은 ROM 공간에 저장되어 있는 문자열 리터럴이므로 문자열을 수정하거나 이어붙이거나 할 수 없다. 그렇다면 어떻게 해야 사용자가 수정할 수 있는 문자열 배열을 만들 수 있을까? 정답은 아래의 코드와 같다.

#include

using namespace std;

int main()  
{  
char\* ptr = "char array literal";

char arr\[\] = "char array";  
arr\[0\] = 'C';

cout << ptr << endl;  
cout << arr << endl;

return 0;  
}

위 코드에서 arr은 사용자가 초기화한 문자열의 크기만큼 자동으로 공간을 할당해 문자열을 담아준다.
이처럼 char* 과 char []의 미묘한 차이점을 잘 알고 실수하는 일이 없도록 해야할 것이다.

'개발언어 > C++' 카테고리의 다른 글

String class 멤버함수 c_str()에서의 주의점  (0) 2020.08.10
switch-case 문에서의 local variable 선언  (0) 2020.08.08
decltype(auto) vs auto  (0) 2020.08.04

C++에서 String class는 C에서의 char_를 손쉽게 조작할 수 있도록 도와주는 STL이다.
다음은 자주 쓰이는 String class의 멤버함수들이다.

함수 명 기능
length 문자열의 길이를 리턴한다.
clear empty 문자열로 초기화한다.
empty empty 문자열인지 확인하여 true/false를 리턴한다.
c_str C string 형식으로 반환한다.
조금 더 많은 정보를 원한다면 다음의 링크를 통해 상세한 내용을 살펴보기 바란다.
위의 설명에서와 같이 c_str은 String class에서 담고있는 문자열을 C에서의 const char* 타입으로 변환하여 반환해주는 편리한 멤버함수이다.
하지만 이 함수를 이용함에 있어 한 가지 주의를 기울여야 한다.
다음의 코드를 통해 이를 살펴보자.
#include <string>

using namespace std;

void printString(const char* str)
{
    cout << str << endl;
}

string makeString()
{
    return string("Temporary Object");
}

int main(void)
{
    string str_loc("Local variable");
    cout << str.c_str() << endl;

    const char* char_loc = str_loc.c_str();
    cout << char_loc << endl;

    str_loc = string("Local variable changed");
    cout << char_loc << endl;

    cout << makeString().c_str() << endl;

    const char* char_temp = makeString().c_str();
    cout << char_temp << endl;

    cout << "Test Done" << endl;
}

위의 코드를 실행시켜 보면 결과는 다음과 같다.

Local variable
Local variable

Temporary Object

Test Done

이는 아마 모든 문자열들이 정상적으로 출력되리라는 독자들의 예상과는 사뭇 다른 결과일 것이다. 이러한 결과가 나타난 이유는 다음과 같다.
String class의 멤버함수인 c_str()은 원본 객체가 담고있는 문자열에 대해 const char* 타입을 리턴한다. 하지만 이 값은 원본 객체인 **string이 메모리를 재할당**받거나 **string 객체가 사라지는 순간** 무효화 된다. 이러한 이유로 위에서 3번째(`Local variable changed`)와 5번째(`Temporary Object`) 출력문이 정상적으로 출력되지 않은 것이다.


따라서 우리는 c_str()을 사용할 때 원본 객체의 값이 변하진 않았는지, 원본 객체가 사라지지 않았는지에 유의하여 사용해야할 것이다. 만약 이러한 고려를 하고싶지 않다면 strdup()를 쓰는 것도 한 가지 방법일 것이다.

'개발언어 > C++' 카테고리의 다른 글

문자열 리터럴: char* vs char []  (0) 2020.08.25
switch-case 문에서의 local variable 선언  (0) 2020.08.08
decltype(auto) vs auto  (0) 2020.08.04

switch-case 문 내에서의 local variable 선언 문제



다음과 같은 코드가 있다고 가정 해 보자


void printVal(int val)
{
    printf("value : %d", val);
}

int main(void)
{
  int val = 0;
  switch(val) {
  case 0:
      int newVal = 10;
      printVal(newVal);
      break;
  case 1:
      pritVal(val);
      break;
  }

  return 0;
}

이 코드는 다음과 같은 에러 메시지를 발생시킬 것이다

initialization of 'newVal' is skipped by 'case' label

그렇다면 이러한 에러 메시지가 발생하는 이유를 살펴보자

  • C 문제점:

    • case문을 만나게되면 이에 해당하는 Label로 jump하게 됨.
    • 하지만 int newVal = 10; 이 부분은 statement가 아니어서 jump 하고자 하는 Label이 될 수 없음
    • case 0 이후의 문장들을 { ... }로 묶어주게 되면 이는 compound statement로 인지되어 jump 가능한 Label이 됨
  • C++문제점

    • C++에서는 variable들은 initialization이 우선 수행되어야 한다.
    • case 1을 만나게 되는 상태에서의 scope에는 newVal이 존재하고 이는 case 0에서 initialization 되므로 현재 initialization이 되어있지 않은 상태이다.
    • 각 case 문들을 { ... }로 묶어주게되면 각각의 scope를 분리시키게 되므로 uninitialization 문제가 발생되지 않는다.

참조: https://stackoverflow.com/questions/92396/why-cant-variables-be-declared-in-a-switch-statement

'개발언어 > C++' 카테고리의 다른 글

문자열 리터럴: char* vs char []  (0) 2020.08.25
String class 멤버함수 c_str()에서의 주의점  (0) 2020.08.10
decltype(auto) vs auto  (0) 2020.08.04

결론

autoconst 타입을 없애버리지만, decltype(auto)const 타입을 유지한다.


다음과 같은 코드를 생각해 보자

#include <iostream>
#include <string>

const std::string _string("New string");

const std::string& func()
{
    return _string;
}

int main(void)
{
    auto str_auto = func();
    str_auto[0] = 'O';
    std::cout << "_string: " << _string << "\t str_auto:" << str_auto << std::endl;

/*
    decltype(auto) str_decl = func();
    str_decl[0] = 'O'; // 주석을 제거하면 이 부분에서 오류가 발생되어 컴파일되지 않음
    std::cout << "_string: " << _string << "\t str_auto:" << str_decl << std::endl;
*/
    return 0;
}

auto 가 const 한정자를 유지하고 있다면 위 str\_auto\[0\] = 'O'; 부분은 실행되지 않을 것이다. 하지만 위 실행의 결과는 다음과 같다.
_string: New string str\_auto:Oew string


다시말해 auto str_auto의 타입은 std::string 타입이고,
decltype(auto) str_decl의 타입은 const std::string& 타입이 된다.

따라서 위에서 auto str_auto 는 임시객체 복제본을 할당받은 것이므로 이에 주의하여야 한다.

+ Recent posts