간단한 시뿔뿔 문제

  • #3758943
    76.***.207.158 1118

    아래 테스트1과 테스트2의 경우, 왜 결과가 그렇게 나오는지 이해가 안가는군요. 혹시 각각다른 아웃풋결과를 이해하기위한 설명이 뭘까요? 그리고 저 함수에서 10을 메인에서 뽑아내는 방법이 있나요?

    1. 테스트1
    #include <iostream>
    using namespace std;

    int& fun()
    {
    int x = 10;
    return x;
    }

    int main()
    {
    cout << “main: ” << fun() << endl;
    return 0;
    }

    2. 테스트2
    #include <iostream>
    using namespace std;

    int& fun()
    {
    int x = 10;
    int& x1 = x;
    return x1;
    }
    int main()
    {
    cout << “main: ” << fun() << endl; // main: 0
    return 0;
    }

    • 00 38.***.241.66

      reddit에 올리면 착한 애들이 알려줍니다.

    • 지나가 174.***.115.150

      이 자식 부랄도 II네

    • J 24.***.187.243

      1번 결과가 본문에 없는것 같지만 2번처럼 하면 안 됩니다. x와 x1은 function이 return 되면 저장된 값을 보장할 수 없는 임시 저장 장소인 stack 혹은 register에 저장되기 때문입니다

      • 76.***.207.158

        테스트1과 테스트2는 테크니컬하게 동일한 함수입니다. 테스트2에서 레퍼런스를 썼기때문에 그냥 동일한 변수값과 동일한 주소를 x1이 가리키게 했을뿐이죠. 문제는 테스트1은 에러도없이 그냥 쌩까버리고, 테스트2는 결과가 나오긴 나오는데 엉뚱하게 나옵니다. 에러메시지도 않나오니 뭐가 문젠지 궁금한겁니다. 사실 레퍼런스의 어떤 단순한 리스트릭션 문제인데 그래서 테스트1부터 문제인데 내가 어떤 단순한 리스트릭션에 대한 뭘 놓치고 있는거 같습니다. 테스트2의 경우도 엉뚱하게 0 가 왜 나오게 된건지 그 내부계산 과정이 궁금하구요.

    • 43 67.***.34.48

      컴파일 환경에 따라 다를수 있겠군요. 상황에 따라 위의 코드는 컴파일에러를 냅니다.
      스택변수의 참조자를 반환하면 안돼요.
      결과값이 왜 다르냐고 묻는다면,
      그건 그냥 가비지값이에요. 항상 그렇게 나온다는 보장이 없어요.

    • 코딩 45.***.55.18

      1번의 경우
      Fun 함수의 반환형이 참조자인데 지역 변수를 반환하고 있습니다. 이렇게 되면 메인에서 리턴받을 함수가 x를 참조하고 싶은데 x은 지역 변수이기 때문에 fun 함수가 끝나면서 메모리에서 소멸하게 됩니다. 그러면 소멸된 메모리를 참조하려는 오류를 범하는 것이죠.
      그래서 x를 반환하는 부분에서 컴파일 경고가 발생합니다. (컴파일 에러가 아닙니다.) 그리고 fun 함수에 접근하려고 하면 런타임 에러가 발생하게 되죠. 이렇게 해제된 메모리를 참조하는 참조자를 댕글링 레퍼런스(Dangling Reference)라고 합니다. 함수에서 참조자를 반환할 때는 지역 변수를 반환하여 댕글링 레퍼런스가 발생하지 않았는지 각별히 주의하여야 합니다.

      2번의경우는 마찬가지입니다.
      해결 방법은 3가지 입니다.

      1. 전역변수 사용, 프로그램이 끝날때까지 할당된 공간을 사용한다. Fun 함수가 끝나도 사용가능
      Int x = 10; 을 메인함수 fun 함수 밖에서 선언

      2. Static 정적 변수 사용
      Fun 함수 내에 static int x=10; 선언
      프로그램이 끝날때까지 할당된 공간을 사용한다. 주의점은 정적변수는 fun 함수 내에서만 사용가능 다른 함수에서 사용 불가능

      3. 동적할당, 반드시 메인함수에서 delete 해주어야함
      Fun 함수내에 동적할당을 해줌, fun 함수가 반환될때도 할당된 공간을 사용가능.

      Int * fun()
      {
      Int* x = new int;
      *x = 10;
      Return x;
      }

      Int main()
      {
      Cout << * fun();
      Delete(fun());
      Return 0;
      }

      Return by address 하고 return by reference 찾아보시기 바랍니다.

      • 76.***.207.158

        설명 감사드립니다.

        그런데 테스트1과 테스트2의 모두의 경우, int& fun() {…} 를 int fun() {…} 로 바꾸어서 살짝 &를 없애버리기만하면, new로 동적할당을 해줄필요도없고 스테틱변수로 선언하지 않아도 로컬변수 10으로 제대로 메인으로 반환이 됩니다. 그러니 설명하신것과 달리 로컬변수라서 메모리에서 사라져버린게 아니죠. 아니면 로컬변수가 함수가 소멸되기전에 다른 메모리로 다시 카피저장된건지….

    • 136.***.48.53

      위의 분들 말씀처럼 function의 return type이 reference인데, stack에 할당된 local variable에 대한 reference를 return하기 때문에 function이 return한 시점에서 stack의 메모리는 할당 해제가 된 상태기 때문에 이 reference를 function 종료 후 참조하면 undefined behavior입니다. 즉, 어떤 결과가 나올지는 compiler가 보장할 수 없다는 것입니다. 해당 메모리가 다른 값으로 덮어 쓰어졌다면 이상한 값을 얻게 되고 접근할 수 없는 메모리인 경우 segmentation fault가 발생합니다.

      이 경우 해결책은 간단히 return type을 int로 변경하는 것입니다. reference type을 return할 이유가 전혀 없는 것 같고, 효율성 면에서도 int는 copy하더라도 비용이 매우 적으며, local variable을 return하면 compiler가 알아서 return value optimization을 최대한 하려고 하기 때문에 효율성에서 문제가 될 염려도 적습니다.

      • 76.***.207.158

        내가 레퍼런스를 제대로 이해했는지를 테스트해보기위해서 일부러 함수에 메인에서 파라미터를 하나도 입력주지않고 함수에서 로컬변수를 밖으로 레퍼런스로 리턴하게 테스트해본거에요. 로컬변수도 그냥 소멸하는지 (물론 new 로 함수안에서 생성시키면 소멸되지 않으니 메모리 리크라는 말이 나왔을테고) 아니면 로컬변수값을 다른데다가 카피저장하고 소멸하는지 ( int fun() {…} 의 경우)그걸 이해하는게 관건인듯하네요. 함수안에서 레퍼런스를 정의하면 로컬변수값은 소멸전에 다른데로 카피저장되지만 그 레퍼런스는 카피저장되지 않고 그냥 소멸되는건지…

        결론적으로 int fun() {…} 로 하면 테스트1과 테스트2가 의도한대로 실행이 제대로 되는데, 왜 int& fun() {…} 로는 로컬변수가 반환이 제대로 안될까…이게 일단 의문점이네요. 사실 메인에서 콜링 타입이 바뀌는지 &fun(); 이나 *(&fun()); 도 그냥 해봤는데 예상대로 안되네요. 확실히 fun(); 로 콜링하는건 틀린건 아닌거같아요.

    • 코딩 45.***.55.18

      해결책은 Return by Value 를 사용하거나 메인함수에서 x 변수를 선언후 해당변수를 참조자로 fun 함수에 파라메다로 넘긴다. 굳이 fun 함수에 선언된 X 변수를 리턴하고 싶을때는 동적할당을 사용하시길 그러나 이건 매우 비효율적입니다 메모리도 많이 잡히고 추후 delete 를 사용안하면 메모리 누수가 발생합니다. 가급적 new 사용권장 안함.

      • 76.***.207.158

        예, 동적할당방법은 스트레이트포워드 해서 이해에 문제는 없는거 같습니다.

    • 531 163.***.132.8

      열심히 설명해 주기 전에, 원 질문 올린 사람이 C++의 reference의 개념을 제대로 이해하는지 부터 확인을 해 보는게 순서인듯.

    • 개장수 47.***.87.130

      뭔 말인지는 모르겠지만 참 좋은 내용이군요.

    • 브래드오디갔노 172.***.27.167

      퐁퐁이들이 이런거 하냐?
      보기만해도 돌아버리겠네 ㅎㅎㅎ

    • 코딩 45.***.55.18

      Int & fun() 함수도 결국은 X 의주소값을 참조는데 함수가 끝나면 X 변수가 메모리에서 삭제되어 int &fun () 이 참조하는 값도 쓰레기값을 참조하게됩니다 모든 지역변수는 함수가 종결되면 메모리에서 소멸됩니다. 함수와 레퍼렌스를 제대로 이해하시면 이렇게 코딩하시면 안됩니다. 함수안에 지역변수는 함수가 끝나는 동시에 소멸하는것은 함수의 기본 원칙입니다. 지역변수는 함수 내부에서 생성되어 스택(Stack)에 저장되며 선언된 함수 내부에서 사용되고 함수가 종료되면 소멸합니다, 지역변수, 전역변수, 함수정의, 리턴 by value, 리턴 by address, 리턴 by 레퍼런스등 천천히 정독하시길 바랍니다.

    • 코딩 45.***.55.18

      아.. 진짜 알아서 공부하세요
      마지막 코멘트입니다

      참조자를 반환하려면 그 객체가 존재해야 한다고 말했다.
      따라서 어차피 함수 안에서 객체가 하나 만들어진다.
      X 변수는 지역 객체이다. 함수가 끝나면 이 객체는 소멸된다.
      따라서 이 함수는 소멸된 객체의 참조자를 리턴하고 있는 것이다. 이참조자가 가리키고 있는 놈은 존재하지 않는다.
      기본부터 다시 공부하세요.

      • 76.***.207.158

        아, 그러니까 로컬변수가 소멸되니까 Int & fun(); 이건 에러가 나는건 알겠는데,
        왜 Int fun(); 경우는 왜 제대로 실행되는지 그게 궁금한거라고요.

    • 코딩 45.***.55.18

      Int fun () 함수쓰는거 풀 코드 올려보세요 한번 봐볼게요 어떻게 사용하시는볼게요 개요

      • 76.***.207.158

        1. 테스트1 변형
        #include <iostream>
        using namespace std;

        int fun() // &를 제거함
        {
        int x = 10;
        return x;
        }

        int main()
        {
        cout << “main: ” << fun() << endl;
        return 0;
        }

        이건 제대로 결과가 나와요. 함수안에 로컬변수를 쓴건데…이건 소멸되기전에 다른주소로 카피/저장된걸로 봐야될것 같기도 한데?

    • 76.***.207.158

      또다른 예;

      테스트3:
      #include <iostream>
      using namespace std;

      int & fun(int& x) { return x; }

      int main()
      {
      int a = 10;
      cout << fun(a);
      return 0;
      }

      테스트4:
      #include <iostream>
      using namespace std;

      int& fun(int& x) { return x+1; }

      int main()
      {
      int a = 10;
      cout << fun(a);
      return 0;
      }

      위 테스트중에 하나는 에러가 나옴. 이유는…? (테스트의 하나는 return x; 이고 다른 하나는 return x+1; 만 다름)

    • 코딩 45.***.55.18

      네 맞습니다. 이 경우는 값으로 반환 (Return by Value) 정의된 함수를 사용합니다
      Return X 는 값이 반환되면, 복사본이 호출자에게 반환됩니다.
      : return 뒤의 ‘객체를 복사하여(이름을 가지지 않은 임시객체형태로)’ 반환됩니다.
      또한 이경우는 복사본을 리턴하기 때문에 R value 입니다. L value는 메모리 위치를 참조하는 식을 의미합니다

      마지막으로 값으로 반환을 사용해야 하는 경우(Return by value)는 아래와 같습니다.

      – 함수 내에서 선언된 (지역)변수를 반환할 때
      – 값으로 전달된 매개 변수를 반환할 때

    • 코딩 45.***.55.18

      테스트 3과 4는 R value, L value 때문에 발생합니다
      테스트 3은 OK, 테스트4는 lvalue 참조 에러
      Return X 의 경우는 변수 x는 L value이며 R value인 상수값 10을 나타내게 되었습니다. 정확히는 val 이 가리키는 메모리 주소에 10이 들어가게 된 것이죠.

      그러나 return X+1 의 경우 리턴할때 x+1 을 어딘가에 저장을 하겠죠 이때 임시저장된 임시 변수는 여전히 L value이지만 x +1 은 그 자체로임시 값이 되어 R value가 되는 것입니다.

      엘벨유 알벨유 공부하시길 바랍니다

    • ㅈ ㅣ나가다 99.***.130.112

      설명 이렇게 열심히 잘해줬는데 이해 안되는거면 C++은 포기하는게 맞는듯.

      • 76.***.207.158

        학교다닐때 보고 시뿔뿔 오랫동안 안보다가 지금 다시 개념정리를 새로 하고 있는데…학교다닐때 수업들을때는 포인터랑 레퍼런스개념이 항상 헷갈렸는데, 지금 찬찬히 다시 보니 왜 그때 이해가 안갔는지 이해가 안감 ㅋㅋㅋ. 사실 그때는 스트라우스트렆의 시뿔뿔 교과서도 제대로 읽어본적이 없었음. 그때는 다른거로 바빠선지 하나하나 내 스스로의 의문점을 테스트해가며 점검해가며 해결할 시간도 없었고… 근데 지금보니 그때 내가 너무 공부를 안했던듯.
        그러나 사실 역시 포인터나 레퍼런스는 헷갈리는 개념중의 하나라는건 부정못함. 더블 트리플 포인터들…게다가 더블앰퍼샌드도 그렇고. rvalue references 나 move semantics 라는 개념은 학교다닐때는 들어보지도 못한 개념이고..지금 처음 읽어보네 ㅋㅋ 이런거 이해못하면 이해하려 조금 더 노력하면 되지 포기운운할 정도로 어려운 주제들은 아님. 개념이 어려워서가 아니라 그냥 책들을 제대로 집중해서 안읽었을뿐. 위에 코딩님은 확실히 개념을 잘 이해하고 있네.

    • 76.***.207.158

      한단계 높여서, 다음의 예의 경우는 더 흥미롭군요.

      테스트 5:
      #include<iostream>
      using namespace std;

      class Test
      {
      public:
      Test(Test &t) { }
      Test() { }
      };

      Test hi()
      {
      cout << “hi() Called\n”;
      Test t;
      return t;
      }

      int main()
      {
      Test t1;
      Test t2 = hi();
      t1 = hi();
      return 0;
      }

      테스트 6:
      #include<iostream>
      using namespace std;

      class Test
      {
      public:
      Test(Test &t) { }
      Test() { }
      };

      Test& hi()
      {
      cout << “hi() Called\n”;
      Test t;
      return t;
      }

      int main()
      {
      Test t1;
      Test t2 = hi();
      t1 = hi();
      return 0;
      }

      이경우는 테스트5번은 컴파일 에러가 나고 테스트 6번은 실행됩니다. Test& hi() 에 &가 있느냐 없느냐의 차이인데, 원글의 테스트와는 달리 &가 있어야 실행되고 없으면 에러가 나는 케이스입니다. 와, 진짜 제대로 이해를 하지 못하면 상당히 헷갈릴수밖에 없지요? 이 경우는 아마 디폴트 컨스트럭터와 카피컨스트럭터의 미묘한 차이가 이런 결과를 만들어 내는거 같네요. 테스트6의 경우 함수반환값으로 템포러리 오브젝트 레퍼런스가 리턴되는데 이게 카피 컨스트럭터에서 lvalue 와 rvalue 가 둘다 Test& 라서 카피가 문제없이 실행 되었을까? 와, 헷갈린다.

      물론 두 테스트 모두 Test(const Test &t){ } 라고 해주면 모두 에러없이 실행된다.