범위 밖에서 지역 변수의 메모리에 접근 할 수 있습니까?


1028

다음 코드가 있습니다.

#include <iostream>

int * foo()
{
    int a = 5;
    return &a;
}

int main()
{
    int* p = foo();
    std::cout << *p;
    *p = 8;
    std::cout << *p;
}

그리고 런타임 예외없이 코드가 실행 중입니다!

출력은 58

어떻게 할 수 있습니까? 함수 외부에서 로컬 변수의 메모리에 액세스 할 수 없습니까?


14
이것은 그대로 컴파일되지 않습니다. 비 성형 비즈니스를 고치더라도 gcc는 여전히 경고합니다 address of local variable ‘a’ returned. valgrind showsInvalid write of size 4 [...] Address 0xbefd7114 is just below the stack ptr
sehe

76
@ Serge : 젊었을 때 나는 Netware 운영 체제에서 실행되는 일종의 까다로운 제로 링 코드를 사용하여 운영 체제에서 정확히 승인하지 않은 방식으로 스택 포인터를 영리하게 움직였습니다. 종종 스택이 화면 메모리와 겹치게되어 바이트가 디스플레이에 바로 쓰여지는 것을 볼 수 있기 때문에 실수했을 때 알고 있습니다. 요즘 그런 종류의 것들을 피할 수는 없습니다.
Eric Lippert

23
lol. 문제가 어디에 있는지 이해하기 전에 질문과 답변을 읽어야했습니다. 실제로 변수의 액세스 범위에 대한 질문입니까? 함수 외부에서 'a'를 사용하지 않아도됩니다. 그리고 그것이 전부입니다. 일부 메모리 참조를 던지는 것은 변수 범위와 완전히 다른 주제입니다.
erikbwork

10
속임수 답변은 속임수 질문을 의미하지 않습니다. 사람들이 여기에서 제안한 많은 속임수 질문은 동일한 근본적인 증상을 나타내는 완전히 다른 질문입니다. 그러나 질문자는 공개 상태를 유지해야한다는 것을 알고 있습니다. 나는 오래된 듀피를 닫고이 질문에 병합하여 매우 좋은 답변을 얻었으므로 열어 두어야합니다.
Joel Spolsky

16
@Joel : 여기에 대한 답변이 좋으면 오래된 질문 으로 병합 해야합니다. 이 질문 은 다른 방법이 아닌 속임수입니다. 그리고이 질문 은 실제로 여기에 제안 된 다른 질문들 중 일부에 속합니다 (제안 된 것 중 일부는 다른 것보다 더 적합하지만). Eric의 대답이 좋다고 생각합니다. (실제로, 나는 이전 질문을 구하기 위해 이전 질문들 중 하나에 답을 병합 한 것에 대해이 질문을 표시했습니다.)
sbi

답변:


4800

어떻게 할 수 있습니까? 함수 외부에서 로컬 변수의 메모리에 액세스 할 수 없습니까?

호텔 방을 임대하십시오. 침대 옆 테이블의 상단 서랍에 책을 넣고 자러갑니다. 다음날 아침에 체크 아웃하지만 열쇠를 돌려주는 것을 잊어 버렸습니다. 열쇠를 훔쳐!

일주일 후, 당신은 호텔로 돌아와 체크인하지 않고 열쇠를 훔친 오래된 방으로 몰래 들어가서 서랍을 봅니다. 당신의 책은 여전히 ​​있습니다. 놀라운!

어떻게 그렇게 될수 있니? 방을 임대하지 않은 경우 호텔 방 서랍의 내용에 액세스 할 수 없습니까?

분명히,이 시나리오는 현실 세계에서 아무 문제없이 일어날 수 있습니다. 더 이상 방에있을 권한이 없을 때 책이 사라지는 신비한 힘은 없습니다. 도난당한 열쇠로 방에 들어 가지 못하게하는 신비한 힘도 없습니다.

호텔 관리자는 책을 제거 할 필요 가 없습니다 . 당신이 물건을 남겨두면 그들은 당신을 위해 그것을 파쇄 할 것이라고 말했다 당신은 그들과 계약을하지 않았다. 열쇠를 훔쳐 열쇠를 가지고 불법적으로 방에 다시 들어간 경우에는 호텔 경비원이 당신을 몰래 잡을 필요 가 없습니다 . "내가 몰래 들어 오려고하면 나중에 방을 막아야합니다. " 오히려, 당신은 계약 "나중에 내 방으로 다시 몰래하지 않겠다고 약속"고 말했다 그들과 계약을 체결 당신이 파산을 .

이 상황에서는 어떤 일이든 일어날 수 있습니다 . 책은 거기있을 수 있습니다. 운이 좋았습니다. 다른 사람의 책이있을 수 있으며 호텔의 화로에있을 수도 있습니다. 당신이 들어 와서 책을 여러 조각으로 찢어 버릴 수 있습니다. 호텔에서 테이블을 제거하고 완전히 예약하여 옷장으로 교체했을 수 있습니다. 호텔 전체가 찢겨지고 축구 경기장으로 교체 될 수 있으며, 몰래 숨을 쉴 때 폭발로 사망 할 수 있습니다.

당신은 무슨 일이 일어날 지 모른다. 당신이 호텔에서 체크 아웃 불법 나중에 사용하기 위해 키를 훔쳐 때, 당신 때문에 예측, 안전한 세상에서 살 권리 포기 시스템의 규칙을 깰 선택합니다.

C ++는 안전한 언어가 아닙니다 . 유쾌하게 시스템의 규칙을 어길 수 있습니다. 만약 당신이 방으로 돌아가는 것처럼 불법적이고 어리석은 짓을하려고한다면 더 이상 없을 수도있는 책상을 통해 들어갈 수 없으며 C ++은 당신을 막을 수 없습니다. C ++보다 안전한 언어는 예를 들어 키를 훨씬 엄격하게 제어하여 전력을 제한함으로써이 문제를 해결합니다.

최신 정보

신의 선하심,이 대답은 많은 주목을 받고 있습니다. (왜 그런지 잘 모르겠다. 나는 그것이 단지 "재미있는"작은 비유라고 생각했다.

나는 약간의 기술적 인 생각으로 이것을 조금 업데이트하는 것이 독창적이라고 생각했다.

컴파일러는 해당 프로그램에 의해 조작 된 데이터의 저장을 관리하는 코드를 생성하는 사업에 있습니다. 메모리를 관리하기 위해 코드를 생성하는 방법에는 여러 가지가 있지만 시간이 지남에 따라 두 가지 기본 기술이 확립되었습니다.

첫 번째는 스토리지에있는 각 바이트의 "수명"즉, 일부 프로그램 변수와 유효하게 연관된 기간을 미리 쉽게 예측할 수없는 일종의 "긴 수명"스토리지 영역을 갖는 것입니다. 시간. 컴파일러는 필요할 때 스토리지를 동적으로 할당하고 더 이상 필요하지 않은 경우이를 회수하는 방법을 알고있는 "힙 관리자"에 대한 호출을 생성합니다.

두 번째 방법은 각 바이트의 수명이 잘 알려진 "짧은 수명"저장 영역을 갖는 것입니다. 여기서 수명은 "중첩"패턴을 따릅니다. 이 수명이 가장 짧은 변수는 다른 수명이 짧은 변수보다 먼저 할당되고 마지막에 해제됩니다. 수명이 짧은 변수는 수명이 가장 긴 변수 다음에 할당되고 변수가 해제되기 전에 해제됩니다. 수명이 짧은 변수의 수명은 수명이 긴 변수의 수명 내에 "중첩"됩니다.

지역 변수는 후자의 패턴을 따릅니다. 메소드가 입력되면 로컬 변수가 활성화됩니다. 해당 메소드가 다른 메소드를 호출하면 새 메소드의 로컬 변수가 활성화됩니다. 첫 번째 방법의 지역 변수가 죽기 전에 죽을 것입니다. 지역 변수와 관련된 스토리지 수명의 시작과 끝의 상대적 순서는 미리 해결할 수 있습니다.

이러한 이유로 로컬 변수는 일반적으로 "스택"데이터 구조의 저장소로 생성됩니다. 스택에는 첫 번째로 푸시 된 속성이 마지막으로 생성 된 속성이 있기 때문입니다.

호텔이 순차적으로 객실을 임대하기로 결정한 것처럼 객실 번호가 높은 사람이 모두 체크 아웃 할 때까지 체크 아웃 할 수 없습니다.

스택에 대해 생각해 봅시다. 많은 운영 체제에서 스레드 당 하나의 스택이 생성되고 스택은 특정 고정 크기로 할당됩니다. 메소드를 호출하면, 내용물이 스택으로 푸시됩니다. 그런 다음 원래 포스터에서와 같이 메소드에서 스택으로 포인터를 다시 전달하면 완전히 유효한 백만 바이트 메모리 블록의 중간에 대한 포인터 일뿐입니다. 우리의 비유로, 당신은 호텔을 체크 아웃합니다; 당신이 할 때, 당신은 방금 가장 높은 자리를 체크 아웃했습니다. 다른 사람이 체크인 한 후 아무도 체크인하지 않고 불법적으로 방으로 돌아 가면 모든 물건 이이 특정 호텔에 여전히 있어야 합니다.

스택은 실제로 저렴하고 쉬우므로 임시 저장소에 스택을 사용합니다. C ++ 구현은 로컬 저장을 위해 스택을 사용하지 않아도됩니다. 힙을 사용할 수 있습니다. 그것은 프로그램을 느리게 만들 것이기 ​​때문에 그렇지 않습니다.

C ++의 구현은 스택에 남겨둔 쓰레기를 건드리지 않고 나중에 불법으로 다시 올릴 수 있도록 요구하지 않습니다. 방금 비운 "방"의 모든 것을 0으로 되 돌리는 코드를 컴파일러가 생성하는 것은 완전히 합법적입니다. 다시 비싸지 않기 때문에 비용이 많이 듭니다.

스택이 논리적으로 축소 될 때 유효했던 주소가 여전히 메모리에 매핑되도록 C ++을 구현할 필요는 없습니다. 구현은 운영 체제에 "지금 우리는이 스택 페이지를 사용하여 완료되었습니다. 달리 말할 때까지 누군가가 이전에 유효한 스택 페이지를 건 드리면 프로세스를 파괴하는 예외를 발행합니다"라고 말할 수 있습니다. 다시, 구현은 느리고 불필요하기 때문에 실제로는 그렇게하지 않습니다.

대신 구현을 통해 실수를 저지르고 벗어날 수 있습니다. 대부분의 경우 언젠가 진정으로 끔찍한 일이 잘못되고 과정이 폭발합니다.

문제가 있습니다. 많은 규칙이 있으며 실수로 규칙을 어기는 것은 매우 쉽습니다. 나는 확실히 여러 번 있습니다. 더 나쁜 것은, 손상이 발생한 후 메모리가 수십억 나노초로 손상된 것으로 밝혀 졌을 때, 메모리를 누가 엉망으로 만들지 알아내는 것이 매우 어려운 경우에만 종종 문제가 발생한다는 것입니다.

더 안전한 메모리 언어는 전원을 제한하여이 문제를 해결합니다. "정상"C #에서는 단순히 로컬 주소를 가져 와서 반환하거나 나중에 저장하는 방법이 없습니다. 당신은 지역의 주소를 취할 수 있지만, 언어는 지역 끝이 끝난 후에는 언어를 사용할 수 없도록 영리하게 설계되었습니다. 로컬 주소를 가져 와서 다시 전달하려면 컴파일러를 특수한 "안전하지 않은"모드로 설정 하고 프로그램에 "안전하지 않은"이라는 단어를 넣어야 할 것입니다. 규칙을 어길 수있는 위험한 것.

더 읽을 거리 :

  • C #에서 참조 반환을 허용하면 어떻게됩니까? 우연히도 이것이 오늘 블로그 게시물의 주제입니다.

    https://ericlippert.com/2011/06/23/ref-returns-and-ref-locals/

  • 왜 스택을 사용하여 메모리를 관리합니까? C #의 값 유형은 항상 스택에 저장됩니까? 가상 메모리는 어떻게 작동합니까? 그리고 C # 메모리 관리자의 작동 방식에 대한 더 많은 주제가 있습니다. 이 기사들 중 다수는 C ++ 프로그래머와도 밀접한 관련이 있습니다.

    https://ericlippert.com/tag/memory-management/


55
@muntoo : 불행히도 운영 체제가 가상 메모리의 페이지를 할당 해제하거나 할당 해제하기 전에 경고 사이렌 소리를내는 것과는 다릅니다. 더 이상 메모리를 소유하지 않을 때 해당 메모리를 사용하는 경우 할당 해제 페이지를 터치 할 때 전체 프로세스를 중단 할 수있는 권한이 운영 체제에 있습니다. 팔!
Eric Lippert

82
@Kyle : 안전한 호텔 만이 그렇게합니다. 안전하지 않은 호텔은 프로그래밍 키에 시간을 낭비하지 않고도 상당한 이익을 얻습니다.
Alexander Torstling

497
@cyberguijarro : C ++는 메모리 안전하지 않다는 것은 단순한 사실입니다. 아무것도 "강타"하지 않습니다. 예를 들어 "C ++은 취하기 어려운 위험한 메모리 모델 위에 쌓인 불특정하고 지나치게 복잡한 기능의 끔찍한 혼란이며 매일 더 이상 제 자신의 정신을 위해 노력하지 않아 고맙습니다." 그것은 C ++을 강타 할 것입니다. 메모리가 안전하지 않다는 것은 원래 포스터에서이 문제가 발생하는 이유를 설명 하는 것입니다. 논설이 아니라 질문에 답하고 있습니다.
Eric Lippert

49
엄밀히 말하면 호텔의 안내 원이 열쇠를 가져 가서 매우 기뻤습니다. "오,이 열쇠를 가져가도 될까요?" "계속하십시오. 왜 신경 쓰겠습니까? 나는 여기서 만 일합니다". 사용하려고 할 때까지 불법이 아닙니다.
philsquared 2016 년

139
적어도 하루는 책을 쓰십시오. 수정되고 확장 된 블로그 게시물 모음 일지라도 구입하고 많은 사람들이 그렇게 할 것이라고 확신합니다. 그러나 다양한 프로그래밍 관련 문제에 대한 독자의 생각이 담긴 책을 읽는 것이 좋습니다. 시간을 찾기가 정말 어렵다는 것을 알고 있지만 글쓰기를 고려하십시오.
Dyppl

275

여기서하는 일은 단순히 주소로 사용되었던 메모리를 읽고 쓰는 것 입니다 a. 이제 외부에 foo있으므로 임의의 메모리 영역에 대한 포인터 일뿐입니다. 그것은 당신의 예에서, 메모리 영역이 존재하고 현재 다른 것을 사용하고 있지 않습니다. 계속해서 사용해도 아무 것도 깨지 않으며, 아직 그것을 덮어 쓴 것은 없습니다. 따라서 5여전히 있습니다. 실제 프로그램에서, 그 메모리는 거의 즉시 재사용 될 것이고 당신은 이것을함으로써 무언가를 깰 것입니다.

에서 돌아 오면 fooOS에 더 이상 해당 메모리를 사용하지 않고 다른 것으로 재 할당 할 수 있다고 알려줍니다. 운이 좋고 다시 할당되지 않고 OS에서 다시 사용하지 않으면 거짓말을 피할 수 있습니다. 기회는 당신이 그 주소로 끝나는 모든 것을 작성하게 될 것입니다.

이제 컴파일러가 왜 불평하지 않는지 궁금하다면 foo최적화에 의해 제거 되었을 것입니다 . 보통 이런 종류의 일에 대해 경고합니다. C 당신이 생각하고있는 것을 알고 있고, 기술적으로는 (에 대한 참조가 없습니다 여기에 범위를 위반하지 않은 가정 a외부의 자체 foo) 만 에러가 아닌 경고를 트리거 전용 메모리 액세스 규칙을.

요컨대 이것은 일반적으로 작동하지 않지만 때로는 우연히 발생합니다.


151

저장 공간이 아직 스톰하지 않았기 때문입니다. 그 행동에 의존하지 마십시오.


1
“진리가 무엇입니까? 그 호텔 서랍에있는 기드온의 성서 일 수도 있습니다. 그리고 어쨌든 그들에게 무슨 일이 있었습니까? 적어도 런던에는 더 이상 존재하지 않습니다. 평등법에 따라 종교관 도서관이 필요하다고 생각합니다.
Rob Kent

나는 오래 전에 쓴 것을 맹세 할 수 있었지만 최근에 튀어 나와서 내 대답이 없다는 것을 알았습니다. 이제 내가 할 때 즐겁게 될 것이라고 기대하면서 위의 암시를 알아 내야합니다.>
msw

1
ㅋ. 영국 최고의 수필가 중 한 사람인 프랜시스 베이컨 (Francis Bacon)은 일부 사람들이 셰익스피어의 연극을 썼다고 의심합니다. 글로버의 아들 인 그 나라의 문법 학교 아이는 천재가 될 수 없기 때문입니다. 영어 수업 시스템입니다. 예수께서는 '나는 진실이다'라고 말씀하셨습니다. oregonstate.edu/instruct/phl302/texts/bacon/bacon_essays.html
Rob Kent

83

모든 답변에 약간의 추가 :

그런 식으로하면 :

#include<stdio.h>
#include <stdlib.h>
int * foo(){
    int a = 5;
    return &a;
}
void boo(){
    int a = 7;

}
int main(){
    int * p = foo();
    boo();
    printf("%d\n",*p);
}

결과는 다음과 같습니다. 7

foo ()에서 돌아온 후 스택이 해제 된 다음 boo ()에 의해 재사용되기 때문입니다. 실행 파일을 디스 어셈블하면 명확하게 볼 수 있습니다.


2
기본 스택 이론을 이해하기위한 단순하지만 훌륭한 예. "int a = 5;"라고 선언하는 테스트 추가 foo ()에서 "정적 int a = 5;" 정적 변수의 범위와 수명을 이해하는 데 사용할 수 있습니다.
제어

15
-1 "for 아마 7 "입니다. 컴파일러는 부울에 등록 할 수 있습니다. 불필요하기 때문에 제거 할 수 있습니다. * p가 5아닐 가능성은 있지만, 7 이되는 특별한 이유가있는 것은 아닙니다 .
Matt

2
이것을 정의되지 않은 행동이라고합니다!
Francis Cugler

왜 그리고 어떻게 스택을 boo재사용 foo합니까? 함수 스택이 서로 분리되어 있지 않으며 Visual Studio 2015 에서이 코드를 실행하는 쓰레기를 얻습니다.
ampawd

1
@ampawd는 거의 1 년이되었지만 "함수 스택"은 서로 분리되어 있지 않습니다. 컨텍스트에는 스택이 있습니다. 해당 컨텍스트는 스택을 사용하여 main에 들어간 다음으로 내려 가서 foo()존재하고으로 내려갑니다 boo(). Foo()그리고 Boo()모두 같은 위치에 스택 포인터를 입력합니다. 그러나 이것은 의존해야 할 행동이 아닙니다. 다른 '물건'(인터럽트처럼, 또는 OS가)의 호출 사이에 스택을 사용할 수 있습니다 boo()foo()그것의 내용을 수정, ...
러스 슐츠

71

C ++에서는 모든 주소에 액세스 할 있지만 반드시해야하는 것은 아닙니다 . 액세스중인 주소가 더 이상 유효하지 않습니다. 그것은 작동 foo는 반환 후 아무것도 다른 메모리를 스크램블 없기 때문에,하지만 많은 상황에서 충돌 할 수 있습니다. Valgrind를 사용 하여 프로그램을 분석 하거나 최적화 된 컴파일을 시도 하십시오 ...


5
당신은 아마 당신이 어떤 주소에 액세스를 시도 할 수 있음을 의미합니다. 오늘날 대부분의 운영 체제는 어떤 프로그램도 어떤 주소에도 액세스하지 못하게하기 때문에; 주소 공간을 보호하기위한 수많은 보호 장치가 있습니다. 이것이 다른 LOADLIN.EXE가없는 이유입니다.
v010dya

66

유효하지 않은 메모리에 액세스하여 C ++ 예외를 발생시키지 않습니다. 임의의 메모리 위치를 참조하는 일반적인 아이디어의 예를 제공합니다. 나는 이와 같이 할 수있다 :

unsigned int q = 123456;

*(double*)(q) = 1.2;

여기서는 단순히 123456을 double의 주소로 취급하여 작성합니다. 여러 가지 일이 발생할 수 있습니다.

  1. q실제로 실제로 double의 유효한 주소 일 수 있습니다 (예 :) double p; q = &p;.
  2. q 할당 된 메모리 내부 어딘가를 가리킬 수 있으며 8 바이트를 덮어 씁니다.
  3. q 할당 된 메모리 외부의 포인트와 운영 체제의 메모리 관리자가 세그먼트 오류 신호를 내 프로그램으로 보내 런타임으로 인해 메모리가 종료됩니다.
  4. 당신은 복권에 당첨됩니다.

설정 방법은 반환 된 주소가 유효한 메모리 영역을 가리키는 것이 더 합리적입니다. 아마 스택에서 조금 더 떨어져 있기는하지만 여전히 액세스 할 수없는 잘못된 위치입니다 결정 론적 패션.

정상적인 프로그램 실행 중에는 메모리 주소의 의미 론적 유효성을 자동으로 확인하지 않습니다. 그러나 메모리 디버거와 같은 작업 valgrind은 행복하게 수행되므로 프로그램을 실행하여 오류를 확인해야합니다.


9
이 프로그램을 계속 실행하는 프로그램을 작성 4) I win the lottery
하겠습니다.

28

옵티 마이저를 사용하여 프로그램을 컴파일 했습니까? 이 foo()함수는 매우 간단하며 결과 코드에서 인라인되거나 대체되었을 수 있습니다.

그러나 결과적인 동작이 정의되지 않았다는 마크 B에 동의합니다.


그건 내기 야 옵티마이 저가 함수 호출을 덤프했습니다.
Erik Aronesty 2016 년

9
필요하지 않습니다. foo () 다음에 새로운 함수가 호출되지 않으므로 로컬 스택 프레임 함수는 아직 덮어 쓰지 않습니다. foo () 뒤에 다른 함수 호출을 추가 5하면 변경됩니다 ...
Tomas

나는 cout을 printf로 바꾸고 stdio를 포함하여 GCC 4.8로 프로그램을 실행했다. "경고 : 로컬 변수 'a'의 주소가 [-Wreturn-local-addr]을 (를) 반환했습니다." 최적화없이 58을 출력하고 -O3으로 08을 출력합니다. 이상한 P는 값이 0이지만 주소를 가지고 있습니다. 주소로 NULL (0)을 예상했습니다.
kevinf

22

당신의 문제는 범위 와 아무 관련이 없습니다 . 표시하는 코드에서 함수 main는 함수 의 이름을 볼 foo수 없으므로 외부 에서이 이름으로 afoo에서 직접 액세스 할 수 없습니다 .foo

당신이 겪고있는 문제는 불법 메모리를 참조 할 때 프로그램이 오류를 알리지 않는 이유입니다. C ++ 표준은 불법 메모리와 법적 메모리 사이에 명확한 경계를 지정하지 않기 때문입니다. 튀어 나온 스택에서 무언가를 참조하면 때때로 오류가 발생하고 때로는 그렇지 않습니다. 때에 따라 다르지. 이 행동에 의존하지 마십시오. 프로그래밍 할 때 항상 오류가 발생한다고 가정하지만 디버그 할 때 오류가 발생하지 않는다고 가정하십시오.


필자 는 IBMTurbo C Programming구본을 회상했다. 필자는 그래픽 메모리를 직접 조작하는 방법과 IBM의 텍스트 모드 비디오 메모리의 레이아웃을 자세히 설명했을 때 어떤 방식 으로든 재생 해왔다. 물론, 코드가 실행되는 시스템은 다른 시스템으로의 이식성에 대해 걱정하지 않는 한 해당 주소에 쓰는 것이 의미하는 바를 명확하게 정의했습니다. IIRC에서 무효에 대한 포인터는 그 책에서 공통된 주제였습니다.
CVn

@Michael Kjörling : 물론입니다! 사람들은 가끔 더러운 일을하고 싶어한다;)
Chang Peng

17

메모리 주소를 반환하고 있습니다. 허용되지만 오류 일 수 있습니다.

해당 메모리 주소를 역 참조하려고하면 정의되지 않은 동작이 발생합니다.

int * ref () {

 int tmp = 100;
 return &tmp;
}

int main () {

 int * a = ref();
 //Up until this point there is defined results
 //You can even print the address returned
 // but yes probably a bug

 cout << *a << endl;//Undefined results
}

동의하지 않습니다.에 문제가 cout있습니다. *a할당되지 않은 (사용 가능한) 메모리를 가리 킵니다. 당신이 그것을 무시하지 않더라도, 그것은 여전히 ​​위험합니다 (그리고 아마도 가짜).
ere

@ereOn : 문제의 의미를 더 명확하게 설명했지만 유효한 C ++ 코드 측면에서 위험하지는 않습니다. 그러나 사용자가 실수를 저지르고 나쁜 일을 할 가능성이 있다는 점에서 위험합니다. 예를 들어 스택이 어떻게 커지는 지 보려고 할 때 주소 값만 신경 쓰고 절대로 역 참조하지 않을 것입니다.
Brian R. Bondy

17

그것은 이틀 전에 논의 되지 않은 고전적인 정의되지 않은 행동 입니다. 사이트를 조금만 검색하십시오. 간단히 말해서 운이 좋았지 만 어떤 일이 있었을 수 있으며 코드가 메모리에 잘못 액세스하고 있습니다.


17

Alex가 지적한 것처럼이 동작은 정의되어 있지 않습니다. 실제로 대부분의 컴파일러는 충돌을 일으키는 쉬운 방법이기 때문에이 작업을 수행하지 않도록 경고합니다.

당신이 무시 무시한 행동의 종류의 예를 들어 가능성이 얻을,이 샘플을 시도 :

int *a()
{
   int x = 5;
   return &x;
}

void b( int *c )
{
   int y = 29;
   *c = 123;
   cout << "y=" << y << endl;
}

int main()
{
   b( a() );
   return 0;
}

이것은 "y = 123"을 출력하지만 결과는 다를 수 있습니다 (실제로!). 포인터가 관련없는 다른 지역 변수를 방해합니다.


17

모든 경고에주의하십시오. 오류 만 해결하지 마십시오.
GCC는이 경고를 보여줍니다

경고 : 로컬 변수 'a'의 주소가 반환되었습니다.

이것이 C ++의 힘입니다. 기억에주의해야합니다. -Werror플래그를 사용하면 이 경고가 오류가되었으며 이제이를 디버그해야합니다.


16

스택을 넣은 이후 스택이 변경되지 않았기 때문에 작동합니다. a다시 액세스하기 전에 다른 함수를 호출하는 다른 함수를 호출 하면 더 이상 운이 좋지 않을 것입니다 ... ;-)


15

실제로 정의되지 않은 동작을 호출했습니다.

임시 작품의 주소를 반환하지만 함수의 끝에서 임시가 파괴되면 액세스 결과가 정의되지 않습니다.

따라서 한 번 있던 a메모리 위치를 수정하는 대신 메모리 위치를 수정했습니다 a. 이 차이는 충돌과 충돌하지 않는 차이와 매우 유사합니다.


13

일반적인 컴파일러 구현에서는 코드를 " a 차지했던 주소로 메모리 블록의 값을 인쇄합니다"라고 생각할 수 있습니다 . 또한 로컬을 구성하는 함수에 새로운 함수 호출을 추가 int하면 값 a(또는 a가리키는 메모리 주소 )이 변경 될 가능성이 큽니다 . 스택이 다른 데이터를 포함하는 새 프레임으로 덮어 쓰기 때문에 발생합니다.

그러나 이것은 정의되지 않은 동작이므로 작동에 의존해서는 안됩니다!


3
" a 차지했던 주소로 메모리 블록의 값을 출력 하는 것은"정확하지 않습니다. 이것은 그의 코드가 잘 정의 된 의미를 갖는 것처럼 들리지만, 그렇지 않습니다. 이것이 아마도 대부분의 컴파일러가 그것을 구현하는 방법 일 것입니다.
Brennan Vincent

@BrennanVincent :가 스토리지를 점유하는 동안 a포인터의 주소는 a입니다. 표준은 구현이 대상의 수명이 끝난 후 주소의 동작을 정의 할 것을 요구하지 않지만, 일부 플랫폼에서 UB가 환경의 문서화 된 방식으로 처리됨을 인식합니다. 지역 변수의 주소는 일반적으로 범위를 벗어난 후에는 많이 사용되지 않지만, 일부 다른 종류의 주소는 해당 대상의 수명이 지난 후에도 여전히 의미가있을 수 있습니다.
supercat

@BrennanVincent : 예를 들어, 표준에서는 구현시 전달 된 포인터 realloc가 반환 값과 비교되도록 허용하거나 이전 블록 내의 주소에 대한 포인터가 새로운 것을 가리 키도록 조정할 것을 요구하지 않을 수 있지만 일부 구현에서는 그렇게합니다. 그리고 이러한 기능을 이용하는 코드는에 할당 된 포인터를 포함하는 동작 (비교조차 포함)을 피해야하는 코드보다 더 효율적일 수 있습니다 realloc.
supercat

13

a범위 ( foo함수) 의 수명 동안 일시적으로 할당되는 변수 이기 때문에 가능 합니다. foo메모리 에서 돌아온 후에 는 여유 공간이 있으며 덮어 쓸 수 있습니다.

당신이하고있는 것은 정의되지 않은 행동으로 설명됩니다 . 결과를 예측할 수 없습니다.


11

:: printf를 사용하지만 cout을 사용하지 않으면 올바른 (?) 콘솔 출력이있는 항목이 크게 변경 될 수 있습니다. 아래 코드 내에서 디버거로 놀 수 있습니다 (x86, 32 비트, MSVisual Studio에서 테스트 됨).

char* foo() 
{
  char buf[10];
  ::strcpy(buf, "TEST”);
  return buf;
}

int main() 
{
  char* s = foo();    //place breakpoint & check 's' varialbe here
  ::printf("%s\n", s); 
}

4

함수에서 돌아온 후 메모리 위치에 값을 유지하는 대신 모든 식별자가 소멸되며 식별자가 없으면 값을 찾을 수 없지만 해당 위치에는 여전히 이전 함수에 저장된 값이 포함됩니다.

그래서, 여기에 기능 foo()의 주소를 반환 a하고 a해당 주소를 반환 한 후 파괴된다. 반환 된 주소를 통해 수정 된 값에 액세스 할 수 있습니다.

실제 예를 들어 보겠습니다.

한 남자가 어떤 위치에서 돈을 숨기고 그 위치를 말해 준다고 가정 해 봅시다. 얼마 후, 돈의 위치를 ​​말한 사람은 죽습니다. 그러나 여전히 당신은 그 숨겨진 돈에 접근 할 수 있습니다.


3

메모리 주소를 사용하는 '더러운'방법입니다. 주소 (포인터)를 반환하면 해당 주소가 함수의 로컬 범위에 속하는지 알 수 없습니다. 주소 일뿐입니다. 이제 'foo'함수를 호출 했으므로 'a'의 해당 주소 (메모리 위치)는 이미 애플리케이션 (프로세스)의 주소 지정 가능한 메모리에 안전하게 할당되어 있습니다. 'foo'함수가 리턴 된 후, 'a'의 주소는 '더러운'것으로 간주 될 수 있지만, 프로그램의 다른 부분 (적어도이 경우에는)의 표현식에 의해 정리되지 않거나 방해 받거나 수정되지 않습니다. AC / C ++ 컴파일러는 이러한 '더러운'액세스를 막지 않습니다 (주의를 기울이면 경고 할 수 있음).


1

코드는 매우 위험합니다. 로컬 변수를 작성 중이며 (함수가 종료 된 후 소멸 된 것으로 간주 됨) 변수가 해제 된 후 해당 변수의 메모리 주소를 리턴합니다.

즉, 메모리 주소가 유효하거나 유효하지 않을 수 있으며 코드는 가능한 메모리 주소 문제 (예 : 분할 오류)에 취약합니다.

즉, 메모리 주소를 포인터로 전달하는 것은 전혀 신뢰할 수 없기 때문에 매우 나쁜 일을하고 있음을 의미합니다.

대신이 예제를 고려하여 테스트하십시오.

int * foo()
{
   int *x = new int;
   *x = 5;
   return x;
}

int main()
{
    int* p = foo();
    std::cout << *p << "\n"; //better to put a new-line in the output, IMO
    *p = 8;
    std::cout << *p;
    delete p;
    return 0;
}

예제와 달리이 예제를 사용하면 다음과 같습니다.

  • 로컬 함수에 int를위한 메모리 할당
  • 해당 메모리 주소는 기능이 만료 된 경우에도 여전히 유효합니다 (아무도 삭제하지 않음)
  • 메모리 주소를 신뢰할 수 있음 (메모리 블록은 사용 가능한 것으로 간주되지 않으므로 삭제 될 때까지 무시되지 않음)
  • 사용하지 않을 때는 메모리 주소를 삭제해야합니다. (프로그램 끝에서 삭제 참조)

기존 답변에서 아직 다루지 않은 내용을 추가 했습니까? 그리고 raw 포인터 /를 사용하지 마십시오 new.
궤도에서 가벼움 경주

1
asker는 원시 포인터를 사용했습니다. 나는 신뢰할 수없는 포인터와 신뢰할 수있는 포인터의 차이점을 볼 수 있도록 그가 한 예제를 정확하게 반영했습니다. 실제로 내 것과 비슷한 또 다른 대답이 있지만 strhopy를 사용합니다 .IMHO는 new를 사용하는 예제보다 초보자 코더에게는 명확하지 않을 수 있습니다.
Nobun

그들은을 사용하지 않았습니다 new. 그들에게 사용하도록 가르치고 있습니다 new. 그러나을 사용해서는 안됩니다 new.
궤도에서 가벼움 경주

따라서 실제로 메모리를 할당하는 것보다 함수에서 파괴되는 로컬 변수에 주소를 전달하는 것이 낫습니까? 이것은 말이되지 않습니다. 메모리 할당 해제의 개념을 이해하는 것이 중요합니다 .imho, 주로 포인터에 대해 묻는 경우 (아스 커는 새로운 포인터를 사용하지 않았지만 포인터를 사용했습니다).
Nobun

내가 언제 그랬어? 아니요, 참조 된 리소스의 소유권을 올바르게 나타내려면 스마트 포인터를 사용하는 것이 좋습니다. new2019 년에 사용하지 마십시오 (라이브러리 코드를 작성하지 않는 한) 새로 온 사람들에게 그렇게 가르치지 마십시오! 건배.
궤도에서 가벼움 경주
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.