C++ 람다함수의 수명?

조회수 3381회
#include <iostream>
#include <string>




using std::cout;
using std::cin;
using std::endl;


decltype(auto) getFunc()
{
    return [](int n1, int n2) { return n1 + n2; };
}


decltype(auto) getFuncPtr()
{
    auto func = [](int n1, int n2) { return n1 + n2; };
    return &func;
}


int main()
{
    auto func = getFunc();

    cout << func(1, 3) << endl;


    auto funcPtr = getFuncPtr();

    cout << (*funcPtr)(2, 4) << endl;


    return 0;
}


위 예제를 비주얼 스튜디오 2015에서 컴파일하여 실행해보면 결과는 잘 나옵니다.
하지만 제가 의문인건
만약 getFuncPtr함수가 평범한 지역 변수(int x 같은..)를 반환했다면
필시 런타임 오류나 버그를 유발할 것인데
지역람다(..?)의 주소를 반환하고 (아마도) 소멸했을 람다를 잘 사용하는걸 보면
람다는 특별취급 되는게 아닌가 해서
좀더 C++ 람다의 깊숙한 부분을 알고싶어져 질문드립니다.

위 예제가 잘 실행되는 이유는 무엇이고
람다는 언제 소멸하나요?

  • (•́ ✖ •̀)
    알 수 없는 사용자

2 답변

  • 일반적으로 스택에 할당되는 지역변수들의 레퍼런스를 리턴하게 되면, 다른 함수 등에서 그 스택 영역을 사용하거나 초기화하였을 경우에 예상치 못한 값을 가질 수 있으므로 크래시가 날 수 있습니다. 물론 함수나 프로그램이 간단하여 해당 영역을 다른 데이터로 덮는 일이 발생하지 않았다면, 제대로 작동되는"것"처럼 보일 수는 있습니다.

    정확히 내부적으로 어떻게 돌아가는지 알기 위해서는 컴파일 후 바이너리를 디스어셈블하는것이 가장 정확하기 때문에, 제공해주신 코드를 컴파일해서 들여다보았습니다. 제가 VS2015 환경은 없어서, 그냥 OSX에서 clang++으로 컴파일했는데, 개인적으로 저도 C++의 lambda들이 컴파일러단에서 어떻게 처리되는지는 확실히 모르지만 조금 의아한 결과를 볼 수 있었습니다. (컴파일러 최적화 옵션에 따라서도 많이 바뀝니다..)

    일단 아래(1)는 main 함수의 디스어셈블리 일부입니다. 보시는바와 같이 *funcPtr를 호출하는 부분에서 어떤 함수(2)를 호출하는것을 보실 수 있죠. main

    이 함수(2)는 총 인자를 3개 받고 (실질적으로는 2개 + this 역할을 하는 포인터 1개) 들어온 실질적 인자 2개를 더하고 리턴합니다. 보시다시피 this로 넘어온 녀석 (ebp+arg_0)이 main에서 보면 getFuncPtr()호출의 반환값인 funcPtr입니다. 근데 전혀 사용되지 않고 있죠. (그래서 스택의 데이터가 덮어씌워져도 크래시가 안나는거죠..) lambda

    즉, (컴파일된 바이너리를 첨부하시면 더 정확히 분석 가능하겠지만) 올려주신 예제에서는 적어도 함수포인터를 호출하는게 아닌 실제 함수로 emit된 코드를 실행하는거라 크래시가 나진 않습니다. 하지만 버그는 버그죠.

    • (•́ ✖ •̀)
      알 수 없는 사용자
  • 위 예제는 실제 메모리가 아직 삭제되지 않아서 일시적으로 작동하는 것 처럼 보이는 것 아닐까요?

    람다 객체는 std::function<> 으로 반환되기 때문에 std::function<> 에 준하는 펑터의 수명을 가집니다. 그러니까 그냥 일반 객체의 수명입니다.

    • (•́ ✖ •̀)
      알 수 없는 사용자
    • 아 그냥 그런 건가요? getFunc의 경우 람다 자체가 복제되어 반환되는거니 문제가 없는거고요? 음. 그렇다면 만약 getFunc함수의 지역변수를 참조형으로 람다가 캡쳐했다면 반환된 람다는 그 참조자를 이용할 수 있나요? 알 수 없는 사용자 2016.4.6 06:57
    • 제가 알기론 내부적으로 Clouser Type 이 Functor 형태로 만들어지기 때문에, 캡쳐된 변수가 펑터의 데이터 멤버로 선언됩니다. 그래서 각 펑터는 고유의 캡쳐변수를 가지게 되는거죠. 아마 실제론 더 복잡하겠지만요. 특히 mutable 이면... 알 수 없는 사용자 2016.4.6 08:44
    • 아 "참조형"으로 캡쳐한 건 원래 변수의 라이프타임이 끝나면 사라지네요. 잘못하면 에러가 나거나 잘못된 값이 사용됩게 됩니다. 알 수 없는 사용자 2016.4.6 08:57

답변을 하려면 로그인이 필요합니다.

프로그래머스 커뮤니티는 개발자들을 위한 Q&A 서비스입니다. 로그인해야 답변을 작성하실 수 있습니다.

(ಠ_ಠ)
(ಠ‿ಠ)