Подтвердить что ты не робот

Каков адрес функции в программе на С++?

В качестве функции задана команда, хранящаяся в одном непрерывном блоке памяти.

И адрес функции (точка входа) - это адрес первой инструкции в функции. (насколько мне известно)

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

Но программа ниже противоречит приведенной выше строке.

код:

#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;
char ** fun()
{
    static char * z = (char*)"Merry Christmas :)";
    return &z;
}
int main()
{
    char ** ptr = NULL;

    char ** (*fun_ptr)(); //declaration of pointer to the function
    fun_ptr = &fun;

    ptr = fun();

    printf("\n %s \n Address of function = [%p]", *ptr, fun_ptr);
    printf("\n Address of first variable created in fun() = [%p]", (void*)ptr);
    cout<<endl;
    return 0;
}

Один пример вывода:

 Merry Christmas :) 
 Address of function = [0x400816]
 Address of first variable created in fun() = [0x600e10]

Итак, здесь адрес функции и адрес первой переменной в функции не такой. Почему так?

Я искал в google, но не могу найти точный требуемый ответ и быть новым для этого языка. Я точно не могу поймать часть содержимого в сети.

4b9b3361

Ответ 1

Итак, здесь адрес функции и адрес первой переменной в функции не такой. Почему так?

Почему это так? Указатель функции - это указатель, указывающий на функцию. Во всяком случае, он не указывает на первую переменную внутри функции.

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

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

Когда программа построена (скомпилирована в файл объекта), другая часть программы организована другим способом.

  • Обычно функция (исполняемый код) находится в отдельном сегменте сегменте кода, обычно это ячейка памяти только для чтения.

  • Переменная времени, определяемая временем компиляции, OTOH, хранится в сегменте данных .

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

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

В этой связи, цитируя статью wiki,

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

Итак, TL; DR, адрес функции - это ячейка памяти внутри сегмента кода (текста), где находятся исполняемые инструкции.

Ответ 2

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

Адреса функций хороши только для двух вещей:

  • для сравнения для равенства p==q и

  • для разыменования и вызова (*p)()

Все, что вы пытаетесь сделать, это undefined, может работать или не работать, и это решение для компилятора.

Ответ 3

Хорошо, это будет весело. Мы можем перейти от чрезвычайно абстрактного понятия о том, что указатель функции находится на С++ вплоть до уровня кода сборки, и благодаря некоторым конкретным путаницам, которые у нас есть, мы даже обсуждаем стеки!

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

  • Мы можем проверить, равны ли два указателя функций. Два указателя функций равны, если они указывают на одну и ту же функцию.
  • Мы можем выполнить тестирование неравенства на этих указателях, что позволяет нам сортировать такие указатели.
  • Мы можем почерпнуть указатель на функцию, что приводит к типу "функции", с которым действительно сложно работать, и на этот раз я буду игнорировать его.
  • Мы можем "вызвать" указатель на функцию, используя используемую вами нотацию: fun_ptr(). Смысл этого идентичен вызову любой функции, на которую указывает.

Это все, что они делают на абстрактном уровне. Под этим, компиляторы могут свободно реализовать его, как они считают нужным. Если компилятор хотел иметь FunctionPtrType, который на самом деле является индексом в какую-то большую таблицу каждой функции в программе, они могли бы.

Однако, как правило, это не так, как это реализовано. При компиляции С++ вплоть до сборки/машинного кода мы стараемся использовать как можно больше архитектурных трюков, чтобы сохранить время выполнения. На реальных компьютерах почти всегда есть операция "косвенного перехода", которая считывает переменную (обычно это регистр) и перескакивает, чтобы начать выполнение кода, который хранится на этом адресе памяти. Его почти универсальные функции скомпилированы в смежные блоки команд, поэтому, если вы когда-либо переходите к первой инструкции в блоке, у нее есть логический эффект вызова этой функции. Адрес первой инструкции выполняется для удовлетворения каждого из сравнений, требуемых абстрактной концепцией С++ указателя на функцию, и это, оказывается, именно то значение, которое аппаратное обеспечение должно использовать косвенный переход для вызова функции! Это настолько удобно, что практически каждый компилятор решает реализовать его таким образом!

Однако, когда мы начинаем говорить о том, почему указатель, который, по вашему мнению, вы смотрели, был таким же, как указатель на функцию, нам нужно вникать в нечто более тонкое: сегменты.

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

Соответственно, обычно существует сегмент .code (с использованием этой точечной нотации просто потому, что ее популярный способ отметить ее во многих архитектурах). В этом сегменте вы найдете весь код. Статические данные будут находиться где-то как .bss. Таким образом, вы можете найти свою статическую строку, хранящуюся довольно далеко от кода, который работает на ней (как правило, не менее 4 килобайт), поскольку большинство современных аппаратных средств позволяет вам устанавливать разрешения на выполнение или запись на уровне страницы: страницы 4kb во множестве современных систем )

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

int fib(int x) {
    if (x == 0)
        return 0;

    if (x == 1)
        return 1;

    return fib(x-1)+fib(x-2);
}

Эта функция вычисляет последовательность Фибоначчи, используя довольно неэффективный, но понятный способ ее выполнения.

У нас есть одна функция, fib. Это означает, что &fib всегда является указателем на одно и то же место, но мы явно вызываем fib много раз, поэтому каждый нуждается в собственном пространстве?

В стеке мы имеем так называемые "фреймы". Фреймы не являются самими функциями, а скорее являются разделами памяти, которые этот конкретный вызов функции разрешен для использования. Каждый раз, когда вы вызываете функцию, например fib, вы выделяете немного места в стеке для своего фрейма (или, более педантично, он будет выделять его после совершения вызова).

В нашем случае fib(x) явно нужно сохранить результат fib(x-1) при выполнении fib(x-2). Он не может хранить это в самой функции или даже в сегменте .bss, потому что мы не знаем, сколько раз оно будет возвращено. Вместо этого он выделяет пространство в стеке для хранения собственной копии результата fib(x-1), а fib(x-2) работает в своем собственном фрейме (используя ту же самую функцию и тот же адрес функции). Когда fib(x-2) возвращается, fib(x) просто загружает это старое значение, которое, несомненно, не было затронуто кем-либо еще, добавляет результаты и возвращает его!

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

Фактически, на большинстве платформ первая инструкция в вашей функции не является первой инструкцией в окончательной скомпилированной версии. Компиляторы вводят несколько дополнительных операций, чтобы управлять указателем стека для вас, чтобы вам даже не пришлось об этом беспокоиться. На некоторых платформах, таких как x86_64, это поведение часто даже обязательно и указано в ABI!

Итак, у нас есть:

  • .code сегмент - где хранятся ваши функциональные инструкции. Указатель функции укажет на первую инструкцию здесь. Этот сегмент обычно помечен как "исполнение/чтение", что предотвращает запись вашей программы после его загрузки.
  • .bss, где ваши статические данные будут сохранены, поскольку он не может быть частью сегмента "выполнить только" .code, если он хочет быть данными.
  • стек - где ваши функции могут хранить фреймы, которые отслеживают данные, необходимые только для этой одной копии, и не более того. (Большинство платформ также используют это для хранения информации о том, куда вернуться после завершения функции)
  • куча - это не появилось в этом ответе, потому что ваш вопрос не содержит никаких операций с кучей. Однако, для полноты, я оставил его здесь, чтобы он вас не удивил позже.

Ответ 4

В тексте вашего вопроса вы говорите:

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

но в коде вы не получаете адрес первой инструкции в функции, а адрес некоторой локальной переменной, объявленной в этой функции.

Функция - это код, переменная - данные. Они хранятся в разных областях памяти; они даже не находятся в одном блоке памяти. Из-за ограничений безопасности, налагаемых операционными системами в настоящее время, код сохраняется в блоках памяти, которые помечены как только для чтения.

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

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

Ответ 5

Как вы говорите, адрес функции может быть (он будет зависеть от системы) адресом первой инструкции функции.

Это ответ. Инструкция не будет передавать адрес с переменными в типичной среде, в которой одно и то же адресное пространство используется для инструкций и данных.

Если они имеют одинаковый адрес, то уничтожение будет уничтожено путем назначения переменных!

Ответ 6

Что такое адрес функции в программе на С++?

Как и другие переменные, адрес функции - это пространство, выделенное для него. Другими словами, это место памяти, где хранятся инструкции (машинный код) для операции, выполняемой функцией.

Чтобы понять это, внимательно изучите макет памяти программы.

Программные переменные и исполняемый код/​​инструкции хранятся в разных сегментах памяти (ОЗУ). Переменные поступают в любой сегмент STACK, HEAP, DATA и BSS, тогда как исполняемый код переходит в сегмент CODE. Посмотрите на общий макет памяти программы

введите описание изображения здесь

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

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

введите описание изображения здесь

Ответ 7

адрес нормальной функции - это то, где начинаются инструкции (если не используется vTable).

Для переменных это зависит:

  • статические переменные хранятся в другом месте.
  • параметры помещаются в стек или хранятся в регистрах.
  • локальные переменные также помещаются в стек или хранятся в регистрах.

Если функция не встроена или не оптимизирована.

Ответ 8

Если я не ошибаюсь, программа загружается в два места в памяти. Первый - исполняемый файл компиляции, включая предопределенные функции и переменные. Это начинается с самой низкой памяти, которую занимает приложение. С некоторыми современными операционными системами это 0x00000, так как менеджер памяти переводит их по мере необходимости. Вторая часть кода - это куча приложений, в которой выделенная дата выполнения, такая как указатели, такая память любого времени выполнения будет иметь разное местоположение в памяти.

Ответ 9

Другие ответы здесь уже объясняют, что такое указатель на функцию и нет. Я специально рассмотрю, почему ваш тест не проверяет, что вы думали.

И адрес функции (точка входа) - это адрес первой инструкции в функции. (насколько мне известно)

Это не требуется (как объясняют другие ответы), но это обычное дело, и обычно это хорошая интуиция.

(В этом случае первая инструкция представляет собой инициализацию переменной.).

Ok.

printf("\n Address of first variable created in fun() = [%p]", (void*)ptr);

То, что вы печатаете здесь, является адресом переменной. Не адрес инструкции, которая устанавливает переменную.

Это не одно и то же. Фактически, они не могут быть одинаковыми.

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

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

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

(Примечание: можно найти систему, в которой эта программа может печатать те же два числа, хотя вы можете легко пройти карьеру программирования, не встречая ее. Гарвардские архитектуры, где код и данные хранятся в разных ячейках памяти. На таких машинах число при печати указателя функции - это адрес в памяти кода, а число при печати указателя данных - это адрес в памяти данных. Два числа могут быть одинаковыми, но это было бы совпадением, и при другом вызове той же функции указатель функции был бы таким же, но адрес переменной мог бы измениться.)

Ответ 10

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