Есть ли безопасный способ указать значение объекта может быть неинициализирован, потому что он никогда не используется? - PullRequest
0 голосов
/ 25 апреля 2018

Отказ от ответственности: ниже чисто академический вопрос; Я держу этот код как минимум в 100 метрах от любой производственной системы. Здесь возникает проблема, которую невозможно измерить ни в одном «реальном» случае.

Рассмотрим следующий код ( Godbolt Link ):

#include <stdlib.h>

typedef int (*func_t)(int *ptr); // functions must conform to this interface

extern int uses_the_ptr(int *ptr);
extern int doesnt_use_the_ptr(int *ptr);

int foo() {
    // actual selection is complex, there are multiple functions,
    // but I know `func` will point to a function that doesn't use the argument
    func_t func = doesnt_use_the_ptr;

    int *unused_ptr_arg = NULL; // I pay a zeroing (e.g. `xor reg reg`) in every compiler
    int *unused_ptr_arg; // UB, gcc zeroes (thanks for saving me from myself, gcc), clang doesn't
    int *unused_ptr_arg __attribute__((__unused__)); // Neither zeroing, nor UB, this is what I want

    return (*func)(unused_ptr_arg);
}

У компилятора нет разумного способа узнать, что unused_ptr_arg не требуется (и поэтому обнуление - это потерянное время), но я делаю, поэтому я хочу сообщить компилятору, что unused_ptr_arg может иметь любое значение, например оказывается в реестре, который будет использоваться для передачи его на func.

Есть ли способ сделать это? Я знаю, что я далеко от стандарта, поэтому я буду в порядке с расширениями, специфичными для компилятора (особенно для gcc и clang).

Ответы [ 4 ]

0 голосов
/ 25 апреля 2018

На самом деле это невозможно во всех архитектурах по очень веской причине.

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

0 голосов
/ 25 апреля 2018

Чтобы исключить возможность обнуления при каждом запуске int foo(), просто наберите unused_ptr_arg static.

int foo() {
    func_t func = doesnt_use_the_ptr;
    static int *unused_ptr_arg;    
    return (*func)(unused_ptr_arg);
}
0 голосов
/ 25 апреля 2018

Использование GCC / Clang `asm` Construct

В GCC и Clang, а также в других компиляторах, поддерживающих расширенный синтаксис сборки GCC, вы можете сделать следующее:

int *unused_ptr_arg;
__asm__("" : "=x" (unused_ptr_arg));

return (*func)(unused_ptr_arg);

То __asm__construct говорит «Вот код ассемблера, который нужно вставить в программу на данном этапе.Он записывает результат в unused_ptr_arg в любом месте, которое вы для него выберете ». (Ограничение x означает, что компилятор может выбрать память, регистр процессора или что-нибудь еще, что поддерживает машина.) Но фактический код сборки пуст ("").Таким образом, ассемблерный код не генерируется, но компилятор считает, что unused_ptr_arg был инициализирован.В Clang 6.0.0 и GCC 7.3 (последние версии в настоящее время в Compiler Explorer) для x86-64 это создает jmp без xor.

Использование стандартного C

Учтите это:

int *unused_ptr_arg;
(void) &unused_ptr_arg;

return (*func)(unused_ptr_arg);

Цель (void) &unused_ptr_arg; - взять адрес unused_ptr_arg, даже если адрес не используется.Это отключает правило в C 2011 [N1570] 6.3.2.1 2, которое гласит, что поведение не определено, если программа использует значение неинициализированного объекта продолжительности автоматического хранения, которое могло быть объявлено с помощью register.Поскольку его адрес взят, он не мог быть объявлен с register, и поэтому использование значения больше не является неопределенным поведением в соответствии с этим правилом.

Вследствие этого объект имеет неопределенное значение.Тогда возникает вопрос, могут ли указатели иметь представление ловушки.Если указатели не имеют представлений ловушек в используемой реализации C, тогда ловушка не будет возникать из-за простой ссылки на значение, как при передаче его в качестве аргумента.

Результат с Clang 6.0.0 в Compiler Explorer - это инструкция jmp без настройки регистра параметров, даже если -Wall -Werror добавлено к опциям компилятора.Напротив, если строка (void) удалена, возникает ошибка компилятора.

0 голосов
/ 25 апреля 2018
int *unused_ptr_arg = NULL;

Это то, что вы должны делать. Вы ничего не платите. Обнуление int не работает. Хорошо, технически это не так, но практически это так. Вы никогда не увидите время этой операции в своей программе. И я не имею в виду, что он такой маленький, что вы этого не заметите. Я имею в виду, что он настолько мал, что многие другие факторы и операции, которые на порядок длиннее, «проглотят» его.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...