#include "pch.h"
#include <iostream>
using namespace std;
void innermostFun(int* ptrValue)
{
*ptrValue = 10;
}
int* middleFun(int* ptrValue)
{
innermostFun(ptrValue);
int someValue = 15;
return &someValue;
}
int main()
{
int value = 0;
int* invalidPtr = middleFun(&value);
cout << *invalidPtr;
return 0;
}
Собственно то почему этот код должен выбросить ошибку написано в статье: http://scrutator.me/post/2015/12/30/pointers_demystified_p2.aspx.
Почему у меня cout << *invalidPtr;
не бросает ошибку?
Вы возвращаете указатель на локальную переменную someValue
, которая после выхода из функции не существует:
int* middleFun(int* ptrValue)
{
innermostFun(ptrValue);
int someValue = 15;
return &someValue;
}
Дальнейшие попытки чтения/записи значения по этому указателю приводят к неопределённому поведению. Но в реальной жизни, скорее всего при чтении вы получите какой-либо мусор или некоторое служебное значение, установленное компилятором для отладочного режима работы.
Небольшой пример:
class task {
public:
explicit task(int _priority)
: m_priority(_priority)
{}
public:
int priority_a() const { return m_priority; }
int priority_b() const { return 0x00; }
protected:
int m_priority;
};
task* create_task() {
task t { 0xFF };
return &t;
}
int main() {
task *t = create_task();
t->priority_b();
return t->priority_a();
}
Скомпилировал с флагом -fomit-frame-pointer
(спасибо @vladnimof), оптимизация выключена.
Метод task::priority_a
:
mov QWORD PTR [rsp-8], rdi // извлекаем указатель на объект
mov rax, QWORD PTR [rsp-8] // ... и как-то его используем
mov eax, DWORD PTR [rax]
ret
Метод task::priority_b
:
mov QWORD PTR [rsp-8], rdi // извлекаем указатель на объект
mov eax, 0 // ... но никак его не используем
ret
Функция main
:
sub rsp, 24
call create_task()
mov QWORD PTR [rsp+8], rax // поместили указатель на объект в стек
mov rax, QWORD PTR [rsp+8] // прочитали из стека :)
mov rdi, rax // передали как первый аргумент
call task::priority_b() const
mov rax, QWORD PTR [rsp+8] // аналогично
mov rdi, rax // аналогично
call task::priority_a() const
nop
add rsp, 24
ret
Функция create_task
:
sub rsp, 24
lea rax, [rsp+12]
mov esi, 255
mov rdi, rax
call task::task(int)
mov eax, 0 // А в eax то уже ноль!
add rsp, 24
ret
Так как внутренние переменные хранятся в стеке и программы ничто не зачищают (оптимизаторы ещё те) то можно хакнуть какую нибудь функцию на пароли / номера карточек и т.д. Ваш код будет работать до тех пока оптимизация компилятора всё не испортит. Чтобы ваш код работал нужно переменную делать volatile (Выполнить несмотря ни на что - есть!). Вот работающий пример использовать память не по назначению:
// > g++ -Wall -Wextra -Wpedantic -Os stacksecret.cpp
// > g++ -Wall -Wextra -Wpedantic -Os -S stacksecret.cpp
// warning: address of local variable ‘s’ returned [-Wreturn-local-addr]
# include <iostream>
typedef
struct ssecret {
int x ;
int y ;
}
secret ;
secret * f ( ) {
secret s ;
s . x = 666 ;
s . y = 999 ;
return & s ; }
/* leaq -8(%rsp), %rax
ret */
secret volatile * v ( ) {
secret volatile s ;
s . x = 666 ;
s . y = 999 ;
return & s ; }
/*movl $666, -8(%rsp)
leaq -8(%rsp), %rax
movl $999, -4(%rsp)
ret */
int main ( ) {
secret * ns = f ( ) ;
std::cout<<"x = "<< ns->x <<" , y = "<< ns->y << std::endl;
ns -> x = 0 ;
ns -> y = 0 ;
secret volatile * s = v ( ) ;
/*movl $666, 8(%rsp)
movl $999, 12(%rsp)*/
std::cout<<"x = "<< s->x <<" , y = "<< s->y << std::endl;
s -> x = 0 ;
s -> y = 0 ;
/*movl $0, 8(%rsp)
movl $0, 12(%rsp)
addq $24, %rsp*/
}
---Вывод---
>./a.out
x = 0 , y = 0
x = 666 , y = 999
В ассемблерном коде видно, что несмотря на оптимизацию в стек всё-таки ставят значения. Функция v возвращает StackPointer-8
как адрес локальной переменной структуры. И можно пользоваться этими данными как угодно. (Раз ничьё - значит моё!) Правильно использовать volatile надо чтобы зачищать память. Вот функция main чистит , знает порядок.
Ваш код не должен падать, так как когда вызывали функцию , там хватало места в стеке, поэтому любой доступ к этим ячейкам памяти свободный, а если компилятор постарается, то вообще к памяти дотрагиваться не будет. Но гарантий сохранности данных нет, так как любая функция может использовать стек по своему усмотрению.
Данный код называется опасным и, да, может привести к неопределенному поведению. Но, в принципе, такой код не просто может работать, а даже может работать правильно, что часто приводит к тяжело отыскиваемой ошибке. Его, конечно, практиковать не стоит.
Просто, основываясь на том, что программе определяется место для стека. А параметры функций передаются в основном через стек, если конечно не указан другой способ передачи, то после выхода из функции и до вызова следующей стек остается, как правило, нетронутым. Там по сути смещается только указатель. А так как мы вернули адрес в стеке, то мы можем по нему получить нормальное значение уже практически несуществующей переменной. Конечно если сразу вызвать другую функцию, то значение наверняка будет затерто. Другими словами если стек не трогать, то значение в нем может жить вполне определенное время.
Практически следующий код будет без проблем выполняться правильно:
int *Func()
{
int a = 10;
return &a;
}
void main()
{
int vvv = *Func(); // vvv будет = 10
}
Пользоваться такой возможностью безусловно не рекомендуется.
Отвечая на Ваш вопрос, указанный Вами код работает именно по этой причине.
Как динамически менять тип у вектора в структуре в зависимости от типа записываемого значения, в него могут записываться значения следующего...
Как можно извлечь одну запись из таблицы RethinkDB на Java? Я попробовал сделать так, как написано в документации:
К сожалению, не смог найти на просторах глобальной сети подходящего для меня объяснения этого понятияКогда заходит речь об абстрактных типах...