Нужна помощь в устранении предупреждения: разыменование указателя типа наказанного нарушит правила строгого наложения имен - PullRequest
0 голосов
/ 04 мая 2019

Я работаю над набором кода C для его оптимизации.Я наткнулся на предупреждение при исправлении неработающего кода.

Среда - Linux, C99, компилируемая с флагами -Wall -O2.

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

    struct text {
        char count[2];
        char head[5];
        char textdata[5];
    }

Код должен возвращать указатель T1 и T2 на ожидаемые строки head и textdata:

int main(void) {
    struct text *T1;
    char *T2;
    char data[] = "02abcdeabcde";

    T1 = (struct text *)data;
    T2 = T1->textdata;
    gettextptr((char *)T1, T2);
    printf("\nT1 = %s\nT2 = %s\n", (char *)T1, T2);
    return (0);
}

void gettextptr(char *T1, char *T2) {
    struct text *p;
    int count;

    p = (struct text *)T1;
    count = (p->count[0] - '0') * 10 + (p->count[1] - '0');

    while (count--) {
        if (memcmp(T2, T1, 2) == 0) {
            T1 += 2;
            T2 += 2;
        }
    }
}

Это не сработало, как ожидалось.Ожидалось, что он возвратит адреса первого «с» и последнего «е».Через GDB я обнаружил, что, как только указатель выполнения возвращается из gettextptr() в родительскую функцию, он не сохраняет адреса T1 и T2.Затем я попробовал другой подход к «Вызову по ссылке» с использованием двойного указателя:

int main(void) {
    struct text *T1;
    char *T2;
    char data[] = "02abcdeabcde";

    T1 = (struct text *)data;
    T2 = T1->textdata;
    gettextptr((char **)&T1, &T2);
    printf("\nT1 = %s\nT2 = %s\n", (char *)T1, T2);
    return (0);
}

void gettextptr(char **T1, char **T2) {
    struct text *p;
    int count;

    p = (struct text *)(*T1);
    count = (p->count[0] - '0') * 10 + (p->count[1] - '0');

    while (count--) {
        if (memcmp(*T2, *T1, 2) == 0) {
            *T1 += 2;
            *T2 += 2;
        }
    }
}

Когда я компилирую этот код с -Wall -O2, я получаю следующее предупреждение GCC:

 pointer.c: In function ‘main’:
 pointer.c:23: warning: dereferencing type-punned pointer will break strict-aliasing rules

Итак:

  1. Был ли код, вызываемый по значению в первом случае?

  2. Разрешено ли (char **) для приведения при сохранении строгогоправила алиасинга?

  3. Чего мне не хватает, чтобы устранить это предупреждение?

1 Ответ

1 голос
/ 04 мая 2019

Строгое правило наложения имен - пункт 6.5 / 7 Стандарта . В основном это говорит о том, что вы можете получить доступ к объекту только через lvalue совместимого типа, возможно, с дополнительными квалификаторами; соответствующий тип со знаком / без знака; массив, структура или тип объединения с одним из этих элементов или тип символа. Полученная вами диагностика говорит о том, что ваш код нарушает это правило, и повторяется несколько раз.

У вас очень рано возникают проблемы с:

    T1 = (struct text *)data;

Это преобразование разрешено, хотя результирующий указатель не гарантированно будет правильно выровнен, но вы не сможете многое сделать с T1, не нарушая строгое правило алиасинга. В частности, если вы разыменовываете его с помощью * или -> - что на самом деле является следующим, что вы делаете - тогда вы получаете доступ к массиву char, как если бы это был struct text. Это недопустимо, хотя обратное было бы другой историей.

Преобразование T1 в char * и доступ к указанному массиву через этот указатель, как вы делаете позже, - вот некоторые из немногих вещей, которые может сделать с ним.

gettextexpr() одинаково (обе версии). Он выполняет преобразование того же типа, что описано выше, и разыменовывает преобразованный указатель при обращении к p->count. Получающееся поведение нарушает строгое правило алиасинга и поэтому не определено. Однако на самом деле GCC жалуется во втором случае на доступ к *T1, как если бы это был char *, тогда как на самом деле это struct text * - другое, отдельное, строгое нарушение псевдонимов.

Итак, в ответ на ваши конкретные вопросы:

  1. Был ли код вызывающим по значению в первом случае?

C имеет только передачи по значению, так что да. В первом случае вы передаете два указателя char по значению, которые затем можно использовать для изменения данных char вызывающего. Во втором случае вы передаете два char * указателя по значению, которые вы можете и использовать для изменения переменных char * вызывающего.

  1. Разрешено ли использование (char **) для приведения при соблюдении строгих правил наложения имен?

Нет, абсолютно нет. Приведение к char * (не char **) может позволить вам получить доступ к представлению объекта через результирующий указатель, потому что разыменование char * создает lvalue символьного типа, но нет типа, который может обычно преобразуется из без последствий для строгого наложения.

  1. Что мне не хватает, чтобы устранить это предупреждение?

Вам не хватает того, что вы пытаетесь сделать принципиально запрещено. C не разрешает доступ к массиву char, как если бы это был период struct text. Тем не менее, компиляторы могут принимать код, который делает это, но его поведение не определено.

Устраните предупреждение, отказавшись от подхода «структура-структура», который в любом случае обеспечивает только притирание синтаксического сахара. На самом деле проще и понятнее избавиться от кастинга и написать:

    count = ((*T1)[0] - '0') * 10 + ((*T1)[1] - '0');

Возможно, еще яснее избавиться от использования заклинаний sscanf:

    sscanf(*T1, "%2d", &count);

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

...