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

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

  • Недопустимый доступ может быть жесткой ошибкой (часто SIGBUS).
  • Неравномерный доступ может быть мягкой ошибкой. Либо исправлено в аппаратном обеспечении, либо для снижения производительности. Или исправлено путем эмуляции в программном обеспечении, для серьезной деградации производительности. Кроме того, атомарность и другие гарантии параллелизма могут быть нарушены, что приводит к незначительным ошибкам.

Вот пример использования типичных настроек для процессора x86 (все используемые 32 и 64-битные режимы):

struct X
{
    short s; /* 2 bytes */
             /* 2 padding bytes */
    int   i; /* 4 bytes */
    char  c; /* 1 byte */
             /* 3 padding bytes */
};

struct Y
{
    int   i; /* 4 bytes */
    char  c; /* 1 byte */
             /* 1 padding byte */
    short s; /* 2 bytes */
};

struct Z
{
    int   i; /* 4 bytes */
    short s; /* 2 bytes */
    char  c; /* 1 byte */
             /* 1 padding byte */
};

const int sizeX = sizeof(struct X); /* = 12 */
const int sizeY = sizeof(struct Y); /* = 8 */
const int sizeZ = sizeof(struct Z); /* = 8 */

Можно минимизировать размер структур путем сортировки (например, структура Z в приведенном выше примере).

ВАЖНОЕ ЗАМЕЧАНИЕ: В стандартах C и C ++ указано, что выравнивание структуры определяется реализацией , Поэтому каждый компилятор может выбрать выравнивание данных по-разному, что приводит к разным и несовместимым макетам данных. По этой причине при работе с библиотеками, которые будут использоваться разными компиляторами, важно понять, как компиляторы выравнивают данные. Некоторые компиляторы имеют параметры командной строки и / или специальные #pragma операторы для изменения настроек выравнивания структуры.

25
задан TemplateRex 1 December 2013 в 17:39
поделиться

3 ответа

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

1
ответ дан Edward Loper 1 December 2013 в 17:39
поделиться

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

Рассмотрим этот код:

int table[5];
bool does_table_contain(int v)
{
    for (int i = 0; i <= 5; i++) {
        if (table[i] == v) return true;
    }
    return false;
}

Классические компиляторы не заметят, что предел вашего цикла записан неправильно и что последняя итерация считывает конец массива. В любом случае, он просто попытается прочесть конец массива и вернет true, если значение после конца массива совпадет.

С другой стороны, постклассический компилятор может выполнить следующий анализ:

  • Первые пять раз в цикле функция может возвращать true.
  • Когда i = 5, код выполняет неопределенное поведение. Следовательно, случай i = 5 можно рассматривать как недоступный.
  • Случай i = 6 (цикл продолжается до завершения) также недоступен, потому что для того, чтобы туда добраться, сначала нужно сделать i = 5, что, как мы уже показали, было недоступно.
  • Следовательно, все достижимые пути кода возвращают true.

Затем компилятор упростит эту функцию до

bool does_table_contain(int v)
{
    return true;
}

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

bool does_table_contain(int v)
{
    if (table[0] == v) return true;
    if (table[1] == v) return true;
    if (table[2] == v) return true;
    if (table[3] == v) return true;
    if (table[4] == v) return true;
    if (table[5] == v) return true;
    return false;
}

И затем он понял, что оценка table[5] не определена, поэтому все, что за этой точкой, недостижимо:

bool does_table_contain(int v)
{
    if (table[0] == v) return true;
    if (table[1] == v) return true;
    if (table[2] == v) return true;
    if (table[3] == v) return true;
    if (table[4] == v) return true;
    /* unreachable due to undefined behavior */
}

, а затем наблюдаем, что все достижимые пути кода возвращают true.

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

T& being_a_bad_boy()
{
    /* unreachable due to undefined behavior */
}

Этот анализ может затем распространяться обратно во всех вызывающих объектах being_a_bad_boy:

void playing_with_fire(bool match_lit, T& t)
{
    kindle(match_lit ? being_a_bad_boy() : t);
} 

Поскольку мы знаем, что being_a_bad_boy недоступен из-за неопределенного поведения, компилятор может заключить, что match_lit никогда не должно быть true , в результате чего

void playing_with_fire(bool match_lit, T& t)
{
    kindle(t);
} 

И теперь все загорается независимо от того, горит ли спичка.

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

84
ответ дан chikuba 1 December 2013 в 17:39
поделиться

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

Если ваш код может привести к недопустимому объекту, то либо он должен вернуть указатель (предпочтительно умный указатель, но это другое обсуждение), используйте шаблон нулевого объекта, упомянутый выше (здесь может быть полезен boost :: extra), или выбросить исключение.

1
ответ дан bdow 1 December 2013 в 17:39
поделиться
Другие вопросы по тегам:

Похожие вопросы: