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

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

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

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

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

код:

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

10 ответов


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

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

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

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

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

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

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

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

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

в связи с этим, процитировать wiki статьи

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

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


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

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

  1. для сравнения на равенство p==q и

  2. разыменовать и вызвать (*p)()

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


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

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

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

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

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

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

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

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

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

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

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

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

так во всем мы имеем:

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

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

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

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

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

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

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


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

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

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


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

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

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

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

enter image description here

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

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

enter image description here


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

на переменные это зависит от:

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

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


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


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

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

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

(в этом случае первый инструкция-это инициализация переменной.).

Ok.

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

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

Это не одно и то же. На самом деле, они не может будет то же самое.

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

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

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

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


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