Строгое правило алиасинга и строгая реализация glibc

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

По их словам (насколько я понимаю), доступ к char Использование буфера с указателем на другой тип нарушает строгое правило алиасинга. Тем не менее, glibc реализация strlen() имеет такой код (с удаленными комментариями и 64-битной реализацией):

size_t strlen(const char *str)
{
    const char *char_ptr;
    const unsigned long int *longword_ptr;
    unsigned long int longword, magic_bits, himagic, lomagic;

    for (char_ptr = str; ((unsigned long int) char_ptr 
             & (sizeof (longword) - 1)) != 0; ++char_ptr)
       if (*char_ptr == '\0')
           return char_ptr - str;

    longword_ptr = (unsigned long int *) char_ptr;

    himagic = 0x80808080L;
    lomagic = 0x01010101L;

    for (;;)
    { 
        longword = *longword_ptr++;

        if (((longword - lomagic) & himagic) != 0)
        {
            const char *cp = (const char *) (longword_ptr - 1);

            if (cp[0] == 0)
                return cp - str;
            if (cp[1] == 0)
                return cp - str + 1;
            if (cp[2] == 0)
                return cp - str + 2;
            if (cp[3] == 0)
                return cp - str + 3;
        }
    }
}

longword_ptr = (unsigned long int *) char_ptr; линия, очевидно, псевдонимы unsigned long int в char, Я не понимаю, что делает это возможным. Я вижу, что код заботится о проблемах выравнивания, поэтому проблем нет, но я думаю, что это не связано со строгим правилом псевдонимов.

Принятый ответ на третий связанный вопрос гласит:

Тем не менее, существует очень распространенное расширение компилятора, позволяющее вам приводить правильно выровненные указатели из char в другие типы и получать к ним доступ, однако это нестандартно.

Единственное, что приходит мне в голову, это -fno-strict-aliasing вариант, это так? Я нигде не смог найти документально подтвержденное, от чего зависят разработчики glibc, и комментарии каким-то образом подразумевают, что это приведение выполнено без каких-либо проблем, так как очевидно, что проблем не будет. Это заставляет меня думать, что это действительно очевидно, и я скучаю по чему-то глупому, но мои поиски провалились.

4 ответа

Решение

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

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

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

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

Формулировка стандарта на самом деле немного более странная, чем фактические реализации компилятора: стандарт C говорит об объявленных типах объектов, но компиляторы видят только указатели на эти объекты. Таким образом, когда компилятор видит приведение char* для unsigned long*, это должно предполагать, что char* на самом деле псевдоним объекта с объявленным типом unsigned long, делая актерский состав правильным.

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

short myString[] = {0x666f, 0x6f00, 0};
size_t length = strlen((char*)myString);    //implementation now invokes undefined behavior!

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

Таким образом, предполагая, что оптимизатор не может вызвать "неопределенное поведение", причина char* для всего остального опасны, это не алиасинг, а выравнивание. На некоторых устройствах странные вещи начинают происходить, если вы пытаетесь получить доступ к смещенному указателю. Аппаратное обеспечение может загружать данные с неправильного адреса, вызывать прерывание или просто очень медленно обрабатывать запрошенную загрузку памяти. Вот почему стандарт C обычно объявляет такие броски неопределенным поведением.

Тем не менее, вы видите, что рассматриваемый код на самом деле решает проблему выравнивания явно (первый цикл, который содержит (unsigned long int) char_ptr & (sizeof (longword) - 1) subcondition). После этого char* правильно выровнен, чтобы быть интерпретирован как unsigned long*,

Конечно, все это не совсем соответствует стандарту C, но это соответствует реализации C компилятора, с которой этот код предназначен для компиляции. Если gcc люди изменили свой компилятор, чтобы воздействовать на этот кусок кода, glibc люди просто будут жаловаться на это достаточно громко, чтобы gcc будет изменен обратно, чтобы правильно обрабатывать этот тип приведения.

В конце концов, стандартные реализации библиотеки C просто должны нарушать строгие правила псевдонимов, чтобы работать должным образом и быть эффективными. strlen() просто нужно нарушать эти правила, чтобы malloc()/free() пара функций должна быть в состоянии занять область памяти, которая имела объявленный тип Fooи превратить его в область памяти объявленного типа Bar, И нет malloc() позвоните внутрь malloc() реализация, которая даст объекту объявленный тип в первую очередь. Абстракция языка Си просто ломается на этом уровне.

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

Функция не пытается что-либо изменить с помощью указателя, поэтому конфликта нет.

Другие вопросы по тегам