Я экспериментирую со стеком C, чтобы проверить некоторые вещи и узнать больше о языке. Я знаю, что функция имеет кадр стека, созданный перемещением указателя rsp/rbp.
Чего я не понимаю, так это того, что когда я создаю char[]
в функции, выхожу из нее и получаю доступ к указателю в другом фрейме стека, кажется, что данные исчезли, но скомпилированный код, похоже, не имеет все, что очищает его.
Пример кода C:
#include <string.h>
#include <stdio.h>
char *save_ptr = 0;
void print_ptr(char *ptr)
{
char increase_stack[200];
printf("%p\n", increase_stack); // Prints 0x1fff0000a0
printf("%s\n", (char *) 0x1fff0000a0); // Prints nothing
printf("%s\n", increase_stack); // Prints nothing
printf("%s\n", save_ptr); // Prints nothing
}
char *create_str(void)
{
char mstring[200];
save_ptr = mstring;
memmove(mstring, "1234567890\0", 11);
printf("%p\n", mstring); // Prints 0x1fff0000a0
printf("%s\n", mstring); // Prints 1234567890
return mstring;
}
int main(void)
{
char *test = create_str();
// test is 0 beacause of GCC
printf("%p\n", test); // Prints 0
test = save_ptr;
printf("%p\n", test); // Prints 0x1fff0000a0
print_ptr(test);
}
Так вот, чего я не понимаю, так это того, что хоть адрес всё тот же (0x1fff0000a0
) и я там ничего не писал, между звонками вроде бы данные пропадали.
Скомпилированная сборка также, похоже, не пишет по этому адресу (см. скомпилированный код по адресу: https://godbolt.org/z/jdrov6Exz).
Для компиляции я использовал следующие флаги: -g -Og -fno-isolate-erroneous-paths-attribute -fno-isolate-erroneous-paths-attribute
Может кто-нибудь объяснить, почему я не вижу «1234567890», написанное print_ptr()
?
МММ спасибо ! Я не думал об этом, но как только на это указывают, это кажется очевидным!
Вам следует прочитать об области действия и времени жизни переменных. Запрещен доступ к памяти переменных после окончания их жизни. Для локальных переменных, помещенных в стек, это происходит после возврата функции. Ваша попытка распечатать содержимое приводит к неопределенному поведению.
Я знаю, что это плохая практика кодирования, но в данном случае нужно было узнать, возможно ли это технически (даже если этого делать не следует). Я создал функцию, которая не использует стек вместо printf, и вижу «1234567890», как и ожидалось.
Re «Не разрешен доступ к памяти переменных после окончания их жизни»: это неправильное понимание того, что говорит стандарт C. Стандарт C гласит, что он ничего не говорит о доступе к объекту после окончания его жизни. Оно не «разрешает» или «не разрешает». Стандарт C не имеет юрисдикции над программистами или их программами и не может запрещать им что-либо делать. Стандарт C определяет только интерпретацию программ C. Важно понимать, что стандарт призван стать отправной точкой для основного языка, чтобы программы могли выходить за рамки того, что он определяет.
ОТ: Обратите внимание, что стек вызовов не определен стандартом C. Стек вызовов — это деталь реализации, специфичная для системы. Разные системы могут действовать по-разному. Таким образом, даже если вы «перепроектируете» свою конкретную систему, это не скажет вам, как работают другие системы. И проще получить и прочитать ABI для вашей системы, чем заниматься реверс-инжинирингом.
Я знаю, что функция имеет кадр стека, созданный перемещением указателя rsp/rbp.
Вы этого не знаете ни из каких спецификаций языка Си, а Си от него не зависит. Если вы изучаете C, вам следует сосредоточиться на концепциях C, а не на деталях реализации языка.
Чего я не понимаю, так это того, что когда я создаю char[] в функции, выхожу из нее и получаю доступ к указателю в другом кадре стека, кажется, что данные исчезли, но скомпилированный код не выглядит иметь что-нибудь, что очищает его.
Наиболее важными понятиями языка C здесь являются продолжительность хранения и время жизни, которые тесно связаны между собой. Любой объект, объявленный внутри блока без ключевого слова static
, имеет автоматический срок хранения. Его время жизни заканчивается, когда завершается выполнение самого внутреннего содержащего блока. В вашем примере это происходит, когда функция create_str
возвращает значение. После этого массив больше не существует с точки зрения C, любой указатель (in) на него становится «неопределенным», и C прямо отрицает определение того, что происходит, когда вы пытаетесь получить к нему доступ.
Так,
Почему строка исчезает из стека при выходе из функции[?]
Он исчезает в том смысле, что срок его жизни истекает, потому что C так говорит. Что на самом деле происходит, когда вы позже попытаетесь получить к нему доступ, зависит от вашей конкретной реализации C, на которую влияют детали вашей программы. Это может привести к сбою. Он может представлять произвольные данные, возможно, даже данные, которые массив содержал незадолго до конца своего существования. В принципе, он может делать все, что позволяет компьютер.
Опять же, я бы посоветовал изучающим язык C не сосредотачиваться на том, как именно это неопределенное поведение проявляется в конкретной программе или почему оно проявляется именно так. То, что вам не нужно делать подобные вещи, является одним из основных моментов программирования на языке высокого уровня. Что вам следует усвоить, так это то, что небезопасно пытаться получить доступ к объектам после окончания их жизни, если у вас нет соответствующей поведенческой гарантии, исходящей из-за пределов языка C.
Но если вы настаиваете на обдумывании деталей реализации, то…
Единственное, чего я не понимаю, так это того, что даже если адрес стильный
0x1fff0000a0
, то между звонками данные как будто исчезают, хотя адрес тот же и я ничего на него не писал.
... на такой машине, как вы предполагаете, вероятно, не происходит какой-либо явной очистки памяти, занятой массивом во время его существования. Но эта память находится в пространстве стека, где ее могут повторно использовать другие функции. В частности, один или оба вызова printf
в вашей программе могут повторно использовать его, прямо или косвенно, оставляя неизвестно что в этом пространстве. И даже ваша функция print_ptr
может подойти, хотя я подозреваю, что на практике эта конкретная функция не подойдет.
Хороший ответ Джон!!!
Ваш вызов функции
printf
также создает стек. Нет оснований ожидать, что он не затронет какую-то область стека, которая больше не используется.