지역 변수의 메모리가 범위 밖에서 액세스 될 수 있습니까?
다음 코드가 있습니다.
#include <iostream>
int * foo()
{
int a = 5;
return &a;
}
int main()
{
int* p = foo();
std::cout << *p;
*p = 8;
std::cout << *p;
}
그리고 코드는 런타임 예외없이 실행 중입니다!
출력은 58
어떻게 될 수 있습니까? 지역 변수의 메모리는 함수 밖에서 접근 할 수 없습니까?
어떻게 될 수 있습니까? 지역 변수의 메모리는 함수 밖에서 접근 할 수 없습니까?
호텔 방을 빌려 요. 당신은 침대 협탁의 상단 서랍에 책을 넣고 잠자리에 든다. 다음날 아침에 체크 아웃하지만 열쇠를 돌려주는 것을 "잊으십시오". 열쇠를 훔쳐!
일주일 후 호텔로 돌아와 체크인하지 않고 훔친 열쇠를 가지고 오래된 방에 몰래 들어가서 서랍을 들여다 봅니다. 당신의 책은 아직 거기에 있습니다. 놀라운!
어떻게 그렇게 될수 있니? 방을 빌리지 않으면 호텔 방 서랍의 내용물에 접근 할 수 없습니까?
음, 분명히 그 시나리오는 현실 세계에서 일어날 수 있습니다. 더 이상 방에있을 권한이 없을 때 책을 사라지게하는 신비한 힘은 없습니다. 훔친 열쇠로 방에 들어가는 것을 막는 신비한 힘도 없습니다.
책을 제거하기 위해 호텔 관리가 필요 하지 않습니다 . 당신은 그들과 계약을 맺지 않았습니다. 당신이 물건을 남겨두면 그들은 당신을 위해 그것을 파쇄 할 것입니다. 훔친 열쇠를 가지고 방에 불법적으로 재 입장 한 경우, 호텔 보안 직원은 당신이 몰래 들어오는 것을 잡을 필요 가 없습니다 . 당신은 그들과 계약을 맺지 않았습니다. 나중에 방에서 나를 막아야합니다. " 오히려 당신은 그들과 "나중에 내 방으로 몰래 들어 가지 않겠다고 약속한다"는 계약서에 서명했고, 당신이 파기 한 계약입니다 .
이 상황에서는 모든 일이 발생할 수 있습니다 . 책이있을 수 있습니다. 운이 좋았습니다. 다른 사람의 책이 거기에있을 수 있고 당신의 책이 호텔의 화로에있을 수 있습니다. 당신이 들어올 때 누군가가 당신의 책을 산산조각 낼 수 있습니다. 호텔은 테이블을 제거하고 완전히 예약하고 옷장으로 교체 할 수있었습니다. 호텔 전체가 무너지고 축구 경기장으로 대체 될 수 있으며, 몰래 돌아 다니는 동안 폭발로 죽을 것입니다.
당신은 무슨 일이 일어날 지 모릅니다. 당신이 호텔에서 체크 아웃 불법 나중에 사용하기 위해 키를 훔쳐 때, 당신 때문에 예측, 안전한 세상에서 살 권리 포기 는 시스템의 규칙을 깰 선택합니다.
C ++는 안전한 언어가 아닙니다 . 유쾌하게 시스템의 규칙을 어길 수 있습니다. 방에 들어갈 권한이없는 방으로 돌아가서 더 이상 없을 수도있는 책상을 뒤적 거리는 것처럼 불법적이고 어리석은 일을하려고한다면 C ++는 당신을 막을 수 없습니다. C ++보다 안전한 언어는 예를 들어 키를 훨씬 더 엄격하게 제어하여 권한을 제한함으로써이 문제를 해결합니다.
최신 정보
세상에,이 답변이 많은 관심을 받고 있습니다. (이유를 잘 모르겠습니다. "재미있는"작은 비유라고 생각했지만 뭐든지 요.)
좀 더 기술적 인 생각으로 이것을 업데이트하는 것이 적절할 것이라고 생각했습니다.
컴파일러는 해당 프로그램에 의해 조작되는 데이터의 저장을 관리하는 코드를 생성하는 업무를 수행합니다. 메모리를 관리하는 코드를 생성하는 방법에는 여러 가지가 있지만 시간이 지남에 따라 두 가지 기본 기술이 확고 해졌습니다.
첫 번째는 스토리지에있는 각 바이트의 "수명"(즉, 일부 프로그램 변수와 유효하게 연관되는 기간)을 미리 쉽게 예측할 수없는 일종의 "장수"스토리지 영역을 갖는 것입니다. 시간. 컴파일러는 필요할 때 동적으로 스토리지를 할당하고 더 이상 필요하지 않을 때 회수하는 방법을 알고있는 "힙 관리자"에 대한 호출을 생성합니다.
두 번째 방법은 각 바이트의 수명이 잘 알려진 "단기"저장 영역을 갖는 것입니다. 여기서 수명은 "중첩"패턴을 따릅니다. 이러한 단기 변수 중 가장 오래 지속되는 변수는 다른 단기 변수보다 먼저 할당되며 마지막에 해제됩니다. 수명이 짧은 변수는 수명이 가장 긴 변수 다음에 할당되며 그 전에 해제됩니다. 수명이 짧은 이러한 변수의 수명은 수명이 긴 변수의 수명 내에 "중첩"됩니다.
지역 변수는 후자의 패턴을 따릅니다. 메서드가 입력되면 해당 지역 변수가 활성화됩니다. 해당 메서드가 다른 메서드를 호출하면 새 메서드의 지역 변수가 활성화됩니다. 첫 번째 메서드의 지역 변수가 죽기 전에 죽습니다. 로컬 변수와 관련된 스토리지 수명의 시작과 끝의 상대적 순서는 미리 계산할 수 있습니다.
이러한 이유로, 스택은 가장 먼저 푸시 된 것이 마지막으로 튀어 나올 것이라는 속성을 가지고 있기 때문에 일반적으로 로컬 변수는 "스택"데이터 구조의 저장소로 생성됩니다.
마치 호텔이 순차적으로 방을 임대하기로 결정하고 방 번호가 더 높은 모든 사람이 체크 아웃 할 때까지 체크 아웃 할 수없는 것과 같습니다.
스택에 대해 생각해 봅시다. 많은 운영 체제에서 스레드 당 하나의 스택을 얻고 스택은 특정 고정 크기로 할당됩니다. 메서드를 호출하면 물건이 스택으로 푸시됩니다. 그런 다음 원래 포스터가 여기에서하는 것처럼 메서드에서 스택에 대한 포인터를 다시 전달하면 완전히 유효한 백만 바이트 메모리 블록의 중간에 대한 포인터 일뿐입니다. 우리의 비유에서, 당신은 호텔에서 체크 아웃합니다. 당신이 할 때, 당신은 가장 높은 번호의 점유 방에서 방금 체크 아웃했습니다. 다른 사람이 당신을 뒤 따르지 않고 당신이 불법적으로 당신의 방으로 돌아 간다면, 당신의 모든 물건은 이 특정 호텔에 여전히있을 것 입니다 .
우리는 정말 싸고 쉽기 때문에 임시 상점에 스택을 사용합니다. 로컬 스토리지에 스택을 사용하기 위해 C ++ 구현이 필요하지 않습니다. 힙을 사용할 수 있습니다. 그렇지 않으면 프로그램이 느려질 수 있기 때문입니다.
C ++의 구현은 나중에 불법적으로 다시 돌아올 수 있도록 스택에 남겨진 쓰레기를 그대로 두는 데 필요하지 않습니다. 방금 비운 "방"의 모든 것을 0으로 되 돌리는 코드를 컴파일러가 생성하는 것은 완벽하게 합법적입니다. 다시 말하지만 그것은 비싸기 때문이 아닙니다.
스택이 논리적으로 축소 될 때 유효했던 주소가 여전히 메모리에 매핑되도록하기 위해 C ++ 구현이 필요하지 않습니다. 구현은 운영 체제에 "이제 스택 페이지를 사용했습니다. 내가 달리 말할 때까지 이전에 유효한 스택 페이지를 건 드리면 프로세스를 파괴하는 예외를 발행하십시오"라고 말할 수 있습니다. 다시 말하지만, 구현은 느리고 불필요하기 때문에 실제로 그렇게하지 않습니다.
대신 구현을 통해 실수를 저지르고이를 피할 수 있습니다. 대부분. 언젠가 정말 끔찍한 일이 잘못되고 그 과정이 폭발적으로 진행됩니다.
이것은 문제가 있습니다. 많은 규칙이 있으며 실수로 규칙을 어기는 것은 매우 쉽습니다. 나는 확실히 여러 번 있습니다. 더욱이 문제는 손상이 발생한 후 수십억 나노초 동안 메모리가 손상된 것으로 감지되는 경우에만 종종 발생하며, 누가 그것을 엉망으로 만들 었는지 파악하기가 매우 어렵습니다.
더 많은 메모리 안전 언어는 사용자의 권한을 제한하여이 문제를 해결합니다. "일반적인"C #에서는 로컬 주소를 가져와 반환하거나 나중에 저장하는 방법이 없습니다. 당신은 지역의 주소를 취할 수 있지만 언어는 영리하게 설계되어 지역의 수명이 끝난 후에는 사용할 수 없습니다. 로컬 주소를 가져 와서 다시 전달하려면 컴파일러를 특별한 "안전하지 않은"모드로 설정 하고 프로그램에 "안전하지 않은"이라는 단어를 넣어야합니다. 규칙을 위반할 수있는 위험한 것.
추가 정보 :
C #에서 참조 반환을 허용했다면 어떻게 되나요? 공교롭게도 이것이 오늘 블로그 게시물의 주제입니다.
http://blogs.msdn.com/b/ericlippert/archive/2011/06/23/ref-returns-and-ref-locals.aspx
메모리를 관리하기 위해 스택을 사용하는 이유는 무엇입니까? C #의 값 형식은 항상 스택에 저장됩니까? 가상 메모리는 어떻게 작동합니까? 그리고 C # 메모리 관리자의 작동 방식에 대한 더 많은 주제. 이 기사의 대부분은 C ++ 프로그래머에게도 관련이 있습니다.
https://blogs.msdn.microsoft.com/ericlippert/tag/memory-management/
What you're doing here is simply reading and writing to memory that used to be the address of a
. Now that you're outside of foo
, it's just a pointer to some random memory area. It just so happens that in your example, that memory area does exist and nothing else is using it at the moment. You don't break anything by continuing to use it, and nothing else has overwritten it yet. Therefore, the 5
is still there. In a real program, that memory would be re-used almost immediately and you'd break something by doing this (though the symptoms may not appear until much later!)
에서 돌아 오면 foo
OS에 더 이상 해당 메모리를 사용하지 않으며 다른 메모리에 재 할당 될 수 있음을 알립니다. 운이 좋고 다시 할당되지 않고 OS가 다시 사용하는 것을 포착하지 못하면 거짓말을하게됩니다. 그 주소로 끝나는 다른 모든 것을 덮어 쓰게 될 가능성이 있습니다.
이제 컴파일러가 불평하지 않는 이유가 궁금하다면 아마도 foo
최적화에 의해 제거 되었기 때문일 것입니다 . 일반적으로 이런 종류의 것에 대해 경고합니다. C는 당신이 무엇을하고 있는지 알고 있다고 가정하고 기술적으로 여기서 범위를 위반하지 않았고 ( a
외부에 대한 참조는 없음 foo
), 메모리 액세스 규칙만을 위반하지 않고 오류가 아닌 경고 만 트리거합니다.
In short: this won't usually work, but sometimes will by chance.
Because the storage space wasn't stomped on just yet. Don't count on that behavior.
A little addition to all the answers:
if you do something like that:
#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);
}
the output probably will be: 7
That is because after returning from foo() the stack is freed and then reused by boo(). If you deassemble the executable you will see it clearly.
In C++, you can access any address, but it doesn't mean you should. The address you are accessing is no longer valid. It works because nothing else scrambled the memory after foo returned, but it could crash under many circumstances. Try analyzing your program with Valgrind, or even just compiling it optimized, and see...
You never throw a C++ exception by accessing invalid memory. You are just giving an example of the general idea of referencing an arbitrary memory location. I could do the same like this:
unsigned int q = 123456;
*(double*)(q) = 1.2;
Here I am simply treating 123456 as the address of a double and write to it. Any number of things could happen:
q
might in fact genuinely be a valid address of a double, e.g.double p; q = &p;
.q
might point somewhere inside allocated memory and I just overwrite 8 bytes in there.q
points outside allocated memory and the operating system's memory manager sends a segmentation fault signal to my program, causing the runtime to terminate it.- You win the lottery.
The way you set it up it is a bit more reasonable that the returned address points into a valid area of memory, as it will probably just be a little further down the stack, but it is still an invalid location that you cannot access in a deterministic fashion.
Nobody will automatically check the semantic validity of memory addresses like that for you during normal program execution. However, a memory debugger such as valgrind
will happily do this, so you should run your program through it and witness the errors.
Did you compile your program with the optimiser enabled? The foo()
function is quite simple and might have been inlined or replaced in the resulting code.
But I agree with Mark B that the resulting behavior is undefined.
Your problem has nothing to do with scope. In the code you show, the function main
does not see the names in the function foo
, so you can't access a
in foo directly with this name outside foo
.
The problem you are having is why the program doesn't signal an error when referencing illegal memory. This is because C++ standards does not specify a very clear boundary between illegal memory and legal memory. Referencing something in popped out stack sometimes causes error and sometimes not. It depends. Don't count on this behavior. Assume it will always result in error when you program, but assume it will never signal error when you debug.
You are just returning a memory address, it's allowed but probably an error.
Yes if you try to dereference that memory address you will have undefined behavior.
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
}
Pay attention to all warnings . Do not only solve errors.
GCC shows this Warning
warning: address of local variable 'a' returned
This is power of C++. You should care about memory. With the -Werror
flag, this warning becames an error and now you have to debug it.
It works because the stack has not been altered (yet) since a was put there. Call a few other functions (which are also calling other functions) before accessing a
again and you will probably not be so lucky anymore... ;-)
That's classic undefined behaviour that's been discussed here not two days ago -- search around the site for a bit. In a nutshell, you were lucky, but anything could have happened and your code is making invalid access to memory.
This behavior is undefined, as Alex pointed out--in fact, most compilers will warn against doing this, because it's an easy way to get crashes.
For an example of the kind of spooky behavior you are likely to get, try this sample:
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;
}
This prints out "y=123", but your results may vary (really!). Your pointer is clobbering other, unrelated local variables.
You actually invoked undefined behaviour.
Returning the address of a temporary works, but as temporaries are destroyed at the end of a function the results of accessing them will be undefined.
So you did not modify a
but rather the memory location where a
once was. This difference is very similar to the difference between crashing and not crashing.
In typical compiler implementations, you can think of the code as "print out the value of the memory block with adress that used to be occupied by a". Also, if you add a new function invocation to a function that constains a local int
it's a good chance that the value of a
(or the memory address that a
used to point to) changes. This happens because the stack will be overwritten with a new frame containing different data.
However, this is undefined behaviour and you should not rely on it to work!
It can, because a
is a variable allocated temporarily for the lifetime of its scope (foo
function). After you return from foo
the memory is free and can be overwritten.
What you're doing is described as undefined behavior. The result cannot be predicted.
The things with correct (?) console output can change dramatically if you use ::printf but not cout. You can play around with debugger within below code (tested on x86, 32-bit, 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);
}
After returning from a function, all identifiers are destroyed instead of kept values in a memory location and we can not locate the values without having an identifier.But that location still contains the value stored by previous function.
So, here function foo()
is returning the address of a
and a
is destroyed after returning its address. And you can access the modified value through that returned address.
Let me take a real world example:
Suppose a man hides money at a location and tells you the location. After some time, the man who had told you the money location dies. But still you have the access of that hidden money.
It's 'Dirty' way of using memory addresses. When you return an address (pointer) you don't know whether it belongs to local scope of a function. It's just an address. Now that you invoked the 'foo' function, that address (memory location) of 'a' was already allocated there in the (safely, for now at least) addressable memory of your application (process). After the 'foo' function returned, the address of 'a' can be considered 'dirty' but it's there, not cleaned up, nor disturbed/modified by expressions in other part of program (in this specific case at least). A C/C++ compiler doesn't stop you from such 'dirty' access (might warn you though, if you care). You can safely use (update) any memory location that is in the data segment of your program instance (process) unless you protect the address by some means.
Your code is very risky. You are creating a local variable (wich is considered destroyed after function ends) and you return the address of memory of that variable after it is destoyed.
That means the memory address could be valid or not, and your code will be vulnerable to possible memory address issues (for example segmentation fault).
This means that you are doing a very bad thing, becouse you are passing a memory address to a pointer wich is not trustable at all.
Consider this example, instead, and test it:
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;
}
Unlike your example, with this example you are:
- allocating memory for int into a local function
- that memory address is still valid also when function expires, (it is not deleted by anyone)
- the memory address is trustable (that memory block is not considered free, so it will be not overridden until it is deleted)
- the memory address should be deleted when not used. (see the delete at the end of the program)
'Development Tip' 카테고리의 다른 글
Python에서 경과 된 시간 측정 (0) | 2020.09.27 |
---|---|
Xcode DMG 또는 XIP 파일을 다운로드하는 방법은 무엇입니까? (0) | 2020.09.27 |
자바 스크립트의 개체 비교 (0) | 2020.09.27 |
전체 경로가 지정된 모듈을 가져 오는 방법은 무엇입니까? (0) | 2020.09.27 |
특정 속성에 대한 LINQ의 Distinct () (0) | 2020.09.27 |