Как получить адрес (указатель) переменной из функции без использования return - PullRequest
0 голосов
/ 01 июля 2019

Для следующего C-кода как я могу получить адрес (указатель) a из функции foo() в функцию main()?

  • По некоторым причинам я не могу использовать return в foo()
  • Из main() функции, я не знаю тип данных a
void foo(void *ptr){
    int a = 12345;
    ptr = &a;
    printf("ptr in abc: %d\n",ptr);
}

int main() {

    void *ptr;
    foo(ptr);

    printf("ptr in main: %d\n",ptr);
    //printf("a in main: %d\n",*ptr);       //print the value of a (ie. 12345)


    return 0;
}

Ответы [ 4 ]

4 голосов
/ 01 июля 2019

Как получить [что-нибудь] из функции без использования return

Способ получить что-то изнутри функции во внешнюю среду без возврата - использовать косвенное обращение.Передать указатель на некоторый объект в качестве аргумента и косвенно через указатель внутри функции, чтобы установить значение указанного объекта.

Из функции main () я не знаю тип данныхa

Вы можете указать на любой объект, используя указатель void, без необходимости знать тип объекта.

Чтобы соединить эти вещи вместе:

int main(void) {
    void* ptr;  // a variable to store the address
    foo(&ptr);  // pass pointer to the variable
                // ptr now points to where a used to be
}

void foo(void** ptr){
    int a = 12345;
    *ptr = &a;  // set the pointed variable
}

Самое главное, однако : локальный объект a больше не существует после того, как foo вернулся, и поэтому указатель болтается, и с ним мало что можно сделать.Таким образом, это довольно бессмысленное упражнение.

1 голос
/ 01 июля 2019

Есть две основные проблемы с вашей функцией foo.

Первая причина, по которой программа не компилируется, это тип возврата foo.Поскольку это void, вы не можете возвращать из него никаких значений.

Другая проблема, которая приведет к неопределенному поведению, заключается в том, что ваша переменная a выходит за пределы области видимости.Если вы хотите получить к нему доступ после того, как он выходит из области видимости, он должен быть размещен в куче (например, с новым).

0 голосов
/ 01 июля 2019

Чтобы понять, ПОЧЕМУ вам не следует пытаться вернуть указатель на локальную переменную, вам необходимо визуализировать, как локальные переменные размещаются в первую очередь.

Локальные переменные размещаются в STACK.Стек является зарезервированной областью памяти, имеющей основное назначение, оставляя «крошечный» след адресов памяти, по которому ЦП должен переходить после завершения выполнения подпрограммы.

Перед вводом подпрограммы (обычно с помощью инструкции CALL на машинном языке в архитектурах x86) ЦПУ помещает в стек адрес инструкции сразу после ВЫЗОВА.

ret_address_N
. . . . . . .
ret_address_3
ret_address_2
ret_address_1

Когда подпрограмма заканчивается, инструкция RET urn заставляет процессор извлекать самый последний адрес из стека и перенаправляет выполнение путем перехода к нему, эффективно возобновляя выполнение подпрограммы или функции, которая инициировала вызов.

Такое расположение стека очень мощное, поскольку оно позволяет вам вкладывать большое количество независимых вызовов подпрограмм (позволяя создавать универсальные библиотеки многократного использования), а также позволяет рекурсивные вызовы, когда функция может вызывать себя (напрямую или косвенно)., вложенной подпрограммой).

Кроме того, ничто не мешает помещать пользовательские данные в стек (для этого есть специальные инструкции процессора) КАК ДОЛГО СОСТОЯНИЕ СТЕКОВОГО СОСТОЯНИЯВОССТАНОВЛЕНО ПЕРЕД ВОЗВРАТОМ ИЗ СУБРУТИНЫ , прочееВ случае, когда инструкции RET выдвигают ожидаемый адрес возврата, он извлекает мусор и пытается перейти к нему, скорее всего, произойдет сбой.(Кстати, это также то, сколько вредоносных программ работает, перезаписывая стек с действительным адресом и заставляя ЦПУ переходить к вредоносному коду при выполнении инструкции RET)

Может использоваться эта функция стека,например, чтобы сохранить исходное состояние регистров ЦП, которые были изменены внутри подпрограммы - что позволяет коду восстанавливать их значения до выхода из подпрограммы, чтобы подпрограмма вызывающей стороны могла видеть регистры в том же состоянии, в котором они находились, ДО выполнения подпрограммыCALL.

Такие языки, как C, также используют эту функцию для распределения локальных переменных путем установки стекового фрейма .Компилятор в основном суммирует, сколько байтов требуется для учета каждой локальной переменной в определенной подпрограмме, и будет выдавать инструкции ЦП, которые сместят вершину стека на эту вычисленную сумму в байтах при вызове подпрограммы.Теперь каждая локальная переменная может быть доступна как относительно смещения к текущему состоянию стека.

-------------
-------------   local variables for subroutine N
-------------
ret_address_N
-------------   local variables for subroutine 3
ret_address_3
-------------   local variables for subroutine 2
-------------
ret_address_2
-------------
-------------   local variables for subroutine 1
-------------  
-------------
ret_address_1

Помимо выдачи инструкций по настройке кадра стека (эффективное выделение локальных переменных в стеке и сохранение текущих значений регистров), компилятор C будет выдавать инструкции, которые будут восстанавливать состояние стека до его исходного состояния до того, каквызов функции, поэтому инструкция RET может найти в верхней части стека правильный адрес памяти, когда в ней появляется значение, к которому следует перейти.

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

0 голосов
/ 01 июля 2019

По некоторым причинам я не могу использовать return в foo()

, поскольку вы объявили foo как возвращающий тип void.Если у вас есть такая возможность, вы можете использовать ее:

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

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

Из main() функции, я не знаю тип данных a

Верно, потому что вы явно объявилиуказатель как void* и, таким образом, стер тип данных.Объявите правильный тип данных, чтобы избежать этого.

Короче говоря, нет причины использовать параметр void* вместо возвращаемого значения int:

int foo() {
    int a = 42;
    return a;
}

int main(void) {
    int a = foo();
    printf("a in main: %d\n", x);
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...