Post

(C/C++) print를 하거나 문자열을 리턴하는 함수. 어떻게 구성하는게 좋은가? - string을 리턴하자.

보통 “input은 인자, output은 리턴”으로 작성하는 편이 알아보기 쉽기 때문에 함수 인자로 포인터를 넘겨 이 포인터에 쓰는 방식으로 간접적으로 값을 되돌려 주는 것 보다, 명확하게 어떤 값을 리턴하도록 작성하는 편이다.

그러나 C같이 garbage collection이 안되는 언어에서는 어떤 값을 리턴하도록 작성하면 여러가지 이슈를 겪는 경우가 있다.

*** 또한 GC가 안되는 언어에서는 in/output을 명확히 작성한다 해도 반드시 순수 함수라고는 볼 수 없다. 함수 내부에서 malloc() 등을 호출하면 side-effect이 생기기 때문.

예를 들면 다음과 같은 상황이다.

malloc() : 문제점 - 바깥에서 free()
1
2
3
4
5
char* parse_something(const char* dev) {
    char* ret_str = (char*)malloc(PATH_LEN);
    snprintf(ret_str, PATH_LEN, "/aaa/%s/ccc", dev);
    return ret_str;
}

지역 변수로 쓰는건 당연히 안되니, 보통은 두 가지 방법을 생각하게 된다.

  1. 안에서 malloc()하고 이를 리턴하거나,
  2. 함수 argument에 char\*를 하나 더 받아서 간접적으로 돌려주거나.

위 코드 같은 경우 1번 케이스에 해당하는데, C는 GC가 안되므로 이렇게 작성하면 상위 함수에서 반드시 free() 해주어야 한다는 부담이 있다.

static __thread : 문제점 - Reentrancy

1번 케이스를 static \__thread을 사용해 다음과 같이 바꿀 수 있으나 이 것도 좋은 방법은 아니다.

1
2
3
4
5
char* parse_something(const char* dev) {
    static __thread char ret_str[PATH_LEN];
    snprintf(ret_str, PATH_LEN, "/aaa/%s/ccc", dev);
    return ret_str;
}

*** \__thread를 붙여주는 이유는, static 변수는 전역적으로 1개만 존재하기 때문에 멀티 스레드 환경에서 safe하지 않기 때문이다. lock을 걸기에는 너무 비싼 경우 사용한다. *** \__thread 지시어를 붙여주면해당 변수 또는 객체를 스레드 마다 독립적으로 갖게 된다.

*** 그래서 생성자가 있는 객체에는 붙일 수 없다.

* 주의1 )static변수의 크기를 지정하는 값 c PATH_LEN은 반드시 #define이나 15같은 상수여야 한다. static const PATH\_LEN로 선언되어 있는 경우 ` Storage size of ‘ret_str’ isn’t constant` 에러가 발생한다.

thread-safe하게 만들었고, 바깥에서 free()해야 하는지 마는지를 알 수 없는 문제도 해결했다. 그러나 static 변수 포인터를 반환하기 때문에 Reentrancy 문제가 발생한다.

1
2
3
4
char* a = parse_something("kkk");
char* b = parse_something("ttt");
printf("%s %s\n", a, b);
>>> /aaa/ttt/ccc /aaa/ttt/ccc

이처럼 a 변수나 b변수나 결국 static변수를 가리키기게 되고, 여기에는 제일 마지막에 호출된 c parse_something()함수의 결과만 들어있기 때문에 ` a변수의 값을 사용하기 위해서는 c memcpy()`를 써서 값을 옮겨두어야 한다는 번거로움이 존재한다.

이처럼 reentrancy 문제를 가지고 있는 함수들은 ether\_aton(), inet_ntoa() 등이 있으며, 재진입 문제를 해결한 버전으로 ether\_aton\_r(), inet_ntop() 등이 있으니 이 쪽을 사용하는 편이 좋다.

포인터로 넘기기 : C에서의 해결책.
1
2
3
void parse_something(const char* dev, const char* ret_str) {
    snprintf(ret_str, PATH_LEN, "/aaa/%s/ccc", dev);
}

thread-safety, 바깥에서 free()해야 하는지 알 수 없는 문제도 해결, reentrancy 문제도 해결. 이 경우 바깥에서 힙에 안잡고 지역변수로 스택에([]) 잡으면 아예 free()를 하지 않아도 된다. (이 경우 포인터 변경은 불가능하기 때문에 copy 스타일의 작업만 가능하다는 제약이 있긴 하다.) 그래서 이런 경우 포인터로 넘기는 식으로 짜는게 여러 귀찮은 문제를 피할 수 있다.

[C++] string을 return해도 괜찮다.

C++의 경우 스마트 포인터를 사용하거나, STL을 사용하면 해결할 수 있다.

std::string을 리턴하면, delete 부담도 해소되고 코드가 깔끔해진다는 장점이 있다.

단점은 string을 리턴하면서 deep copy가 발생해서 느릴 것 같다는 점인데 요즘 컴파일러는 RVO(Return Value Optimization) 을 잘 해주기 때문에 알아서 포인터 인자로 받아서 간접적으로 넘기는 것이나 크게 차이 없는 정도로 최적화해준다. 그래서 그냥 리턴해줘도 된다. 이런 부분은 컴파일러가 알아서 해준다고 믿는 것이 좋다.

This post is licensed under CC BY 4.0 by the author.