При инициализации переменных в C компилятор сохраняет начальное значение в исполняемом файле - PullRequest
2 голосов
/ 12 июня 2019

Когда Инициализация переменных в C Мне было интересно, компилятор делает это так, что когда код загружается во время выполнения, значение уже установлено ИЛИ ему нужно сделать явный вызов ассемблера, чтобы установить егок начальному значению?

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

например

void foo() {
  int c = 1234;
}

Ответы [ 4 ]

4 голосов
/ 12 июня 2019

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

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

В вашем примере и константа 1234, и переменная c будут оптимизированы, поскольку они не используются.

3 голосов
/ 12 июня 2019

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

void take_ptr(int*);

void static_lifetime_var(void)
{
    static int c = 1234;
    take_ptr(&c);
}

x86-64 сборка из gcc -Os:

static_lifetime_var:
        mov     edi, OFFSET FLAT:c.1910
        jmp     take_ptr
c.1910:
        .long   1234

Если он не используется , он обычно исчезает:

void unused(void)
{
    int c = 1234;
}

x86-64 сборка из gcc -Os:

unused:
        ret

Если он используется, может быть, нет необходимости помещать его в фрейм функции (ее локальные переменные в стеке) - может быть возможно напрямую встроить его в инструкцию по сборке или «использовать как немедленный":

void take_int(int);

void used_as_an_immediate(int d) 
{
  int c = 1234;
  take_int(c*d);
}

x86-64 сборка из gcc -Os:

used_as_an_immediate:
        imul    edi, edi, 1234
        jmp     take_int

Если используется как истинный локальный , его необходимо загрузить в пространство, выделенное стеком:

void take_ptr(int*);

void used(int d)
{
  int  c = 1234;
  take_ptr(&c);
}

x86-64 сборка из gcc -Os:

used:
        sub     rsp, 24
        lea     rdi, [rsp+12]
        mov     DWORD PTR [rsp+12], 1234
        call    take_ptr
        add     rsp, 24
        ret

Обдумывая эти вещи Compiler Explorer вместе с некоторыми базовыми знаниями по сборке ваши друзья.

0 голосов
/ 12 июня 2019

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


Как скорректированный дубликат моего ответа на Как во время компиляции внутренняя инициализация переменных работает в c? :

Стандарт не определяет точный способ инициализации.Это зависит от среды, в которой разрабатывается и выполняется ваш код.

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

Инициализированные автоматические переменные будут записываться при каждом достижении их объявления.Скомпилированная программа выполняет некоторый машинный код для этого.

Статические переменные всегда инициализируются и только один раз перед запуском программы.

Примеры из реального мира:

Большинство (если не все) компьютерные системы хранят начальные значения явно (а не нулевых) инициализированных статических переменных в специальном разделе data, который загружается загрузчиком системы в ОЗУ.Таким образом, эти переменные получают свои значения до запуска программы.Статические переменные, не инициализированные явно или имеющие нулевые значения, помещаются в секцию bss и заполняются нулями кодом запуска перед запуском программы.

Многие встроенные системы имеют свою программу в энергонезависимой памяти, котораяне может быть измененоВ таких системах код запуска копирует начальные значения раздела data в выделенное ему пространство в ОЗУ, что приводит к аналогичному результату.Тот же код запуска обнуляет также раздел bss.

Примечание 1: разделы не обязательно должны называться так.Но это часто встречается.

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

Примечание 2: Существуют другие виды продолжительности хранения, см. Главу 6.2.4 стандарта.

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

0 голосов
/ 12 июня 2019

Во-первых, важно иметь общее понимание слова «компилятор», иначе мы можем в конечном итоге спорить бесконечно.

Проще говоря,

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

(Ссылка: Википедия )

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

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

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

Это отвечает на ваш вопрос?

...