Почему строка исчезает из стека при выходе из функции

Я экспериментирую со стеком 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 также создает стек. Нет оснований ожидать, что он не затронет какую-то область стека, которая больше не используется.

Gerhardh 29.07.2024 14:56

МММ спасибо ! Я не думал об этом, но как только на это указывают, это кажется очевидным!

Architek 29.07.2024 14:59

Вам следует прочитать об области действия и времени жизни переменных. Запрещен доступ к памяти переменных после окончания их жизни. Для локальных переменных, помещенных в стек, это происходит после возврата функции. Ваша попытка распечатать содержимое приводит к неопределенному поведению.

Gerhardh 29.07.2024 14:59

Я знаю, что это плохая практика кодирования, но в данном случае нужно было узнать, возможно ли это технически (даже если этого делать не следует). Я создал функцию, которая не использует стек вместо printf, и вижу «1234567890», как и ожидалось.

Architek 29.07.2024 15:02

Re «Не разрешен доступ к памяти переменных после окончания их жизни»: это неправильное понимание того, что говорит стандарт C. Стандарт C гласит, что он ничего не говорит о доступе к объекту после окончания его жизни. Оно не «разрешает» или «не разрешает». Стандарт C не имеет юрисдикции над программистами или их программами и не может запрещать им что-либо делать. Стандарт C определяет только интерпретацию программ C. Важно понимать, что стандарт призван стать отправной точкой для основного языка, чтобы программы могли выходить за рамки того, что он определяет.

Eric Postpischil 29.07.2024 15:05

ОТ: Обратите внимание, что стек вызовов не определен стандартом C. Стек вызовов — это деталь реализации, специфичная для системы. Разные системы могут действовать по-разному. Таким образом, даже если вы «перепроектируете» свою конкретную систему, это не скажет вам, как работают другие системы. И проще получить и прочитать ABI для вашей системы, чем заниматься реверс-инжинирингом.

Support Ukraine 29.07.2024 15:06
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
6
73
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я знаю, что функция имеет кадр стека, созданный перемещением указателя rsp/rbp.

Вы этого не знаете ни из каких спецификаций языка Си, а Си от него не зависит. Если вы изучаете C, вам следует сосредоточиться на концепциях C, а не на деталях реализации языка.

Чего я не понимаю, так это того, что когда я создаю char[] в функции, выхожу из нее и получаю доступ к указателю в другом кадре стека, кажется, что данные исчезли, но скомпилированный код не выглядит иметь что-нибудь, что очищает его.

Наиболее важными понятиями языка C здесь являются продолжительность хранения и время жизни, которые тесно связаны между собой. Любой объект, объявленный внутри блока без ключевого слова static, имеет автоматический срок хранения. Его время жизни заканчивается, когда завершается выполнение самого внутреннего содержащего блока. В вашем примере это происходит, когда функция create_str возвращает значение. После этого массив больше не существует с точки зрения C, любой указатель (in) на него становится «неопределенным», и C прямо отрицает определение того, что происходит, когда вы пытаетесь получить к нему доступ.

Так,

Почему строка исчезает из стека при выходе из функции[?]

Он исчезает в том смысле, что срок его жизни истекает, потому что C так говорит. Что на самом деле происходит, когда вы позже попытаетесь получить к нему доступ, зависит от вашей конкретной реализации C, на которую влияют детали вашей программы. Это может привести к сбою. Он может представлять произвольные данные, возможно, даже данные, которые массив содержал незадолго до конца своего существования. В принципе, он может делать все, что позволяет компьютер.

Опять же, я бы посоветовал изучающим язык C не сосредотачиваться на том, как именно это неопределенное поведение проявляется в конкретной программе или почему оно проявляется именно так. То, что вам не нужно делать подобные вещи, является одним из основных моментов программирования на языке высокого уровня. Что вам следует усвоить, так это то, что небезопасно пытаться получить доступ к объектам после окончания их жизни, если у вас нет соответствующей поведенческой гарантии, исходящей из-за пределов языка C.

Но если вы настаиваете на обдумывании деталей реализации, то…

Единственное, чего я не понимаю, так это того, что даже если адрес стильный 0x1fff0000a0, то между звонками данные как будто исчезают, хотя адрес тот же и я ничего на него не писал.

... на такой машине, как вы предполагаете, вероятно, не происходит какой-либо явной очистки памяти, занятой массивом во время его существования. Но эта память находится в пространстве стека, где ее могут повторно использовать другие функции. В частности, один или оба вызова printf в вашей программе могут повторно использовать его, прямо или косвенно, оставляя неизвестно что в этом пространстве. И даже ваша функция print_ptr может подойти, хотя я подозреваю, что на практике эта конкретная функция не подойдет.

Хороший ответ Джон!!!

Luis Colorado 30.07.2024 20:14

Другие вопросы по теме