Неожиданная оптимизация strlen при совмещении 2-го массива

28

Вот мой код:

#include <string.h>
#include <stdio.h>

typedef char BUF[8];

typedef struct
{
    BUF b[23];
} S;

S s;

int main()
{
    int n;

    memcpy(&s, "1234567812345678", 17);

    n = strlen((char *)&s.b) / sizeof(BUF);
    printf("%d\n", n);

    n = strlen((char *)&s) / sizeof(BUF);
    printf("%d\n", n);
}

Использование gcc 8.3.0 или 8.2.1 с любым уровнем оптимизации, за исключением -O0этого, выводит, 0 2когда я ожидал 2 2. Компилятор решил, что strlenон ограничен b[0]и поэтому никогда не может быть равен или превышать значение, на которое делится.

Это ошибка в моем коде или ошибка в компиляторе?

Это явно не прописано в стандарте, но я думал, что основная интерпретация происхождения указателя заключается в том, что для любого объекта Xкод (char *)&Xдолжен генерировать указатель, который может повторяться по всему X- эта концепция должна сохраняться, даже если Xслучается, что она имеет подмассивы как внутренняя структура.

(Бонусный вопрос, есть ли флаг gcc, чтобы отключить эту конкретную оптимизацию?)

М.М.
источник
4
Ссылка: мой gcc 7.4.0 сообщает о 2 2различных вариантах.
chux - Восстановить Монику
2
@ Все стандартные гарантии, что они находятся по одному и тому же адресу (структура не может иметь начальное заполнение)
MM
3
@ DavidRankin-ReinstateMonica ", что приводит к тому, что границы char (*) [8] ограничиваются b [0]. Но это насколько я понимаю", я думаю, что это прибивает. поскольку s.bограничено b[0]этим, оно ограничено 8 символами, и, следовательно, двумя вариантами: (1) доступ за пределы в случае, если есть 8 ненулевых символов, который является UB, (2) есть нулевой символ, в котором длина меньше 8, следовательно, деление на 8 дает ноль. Таким образом, сборка (1) + (2) компилятор может использовать UB, чтобы дать одинаковый результат в обоих случаях
user2162550
3
Учитывая, что & s == & s.b, результат не может отличаться. Как показывал @ user2162550, strlen () не вызывается, и компилятор дает предположение о том, каким может быть его результат, даже в случае godbolt.org/z/dMcrdy, где компилятор не может его знать. Это ошибка компилятора .
Ale

Ответы:

-1

Есть некоторые проблемы, которые я вижу, и на них может повлиять то, как компилятор решит разметить память.

    n = strlen((char *)&s.b) / sizeof(BUF);
    printf("%d\n", n);

В приведенном выше коде s.b23 массива ввода из массива из 8 символов. Когда вы ссылаетесь на «просто», s.bвы получаете адрес первой записи в 23-байтовом массиве (и первый байт в 8-значном массиве). Когда код говорит &s.b, это запрашивает адрес адреса массива. Под прикрытием компилятор, скорее всего, генерирует некоторое локальное хранилище, хранит там адрес массива и предоставляет адрес локального хранилища strlen.

У вас есть 2 возможных решения. Они есть:

    n = strlen((char *)s.b) / sizeof(BUF);
    printf("%d\n", n);

или

    n = strlen((char *)&s.b[0]) / sizeof(BUF);
    printf("%d\n", n);

Я также попытался запустить вашу программу и продемонстрировать проблему, но и clang, и версия gcc, которую я имею, с любыми -Oопциями, все же работали, как вы ожидали. Что бы это ни стоило, я использую clang версии 9.0.0-2 и gcc версии 9.2.1 на x86_64-pc-linux-gnu).

JonBelanger
источник
-2

Есть ошибки в коде.

 memcpy(&s, "1234567812345678", 17);

например, это рискованно, хотя s начинается с b должно быть:

 memcpy(&s.b, "1234567812345678", 17);

Второй strlen () также имеет ошибки

n = strlen((char *)&s) / sizeof(BUF);

например, должно быть:

n = strlen((char *)&s.b) / sizeof(BUF);

Строка sb, если копируется правильно, должна содержать 17 букв. Не уверен, как структуры хранятся в памяти, если они выровнены. Вы проверили, что sb содержит 17 скопированных символов?

Таким образом, strlen (sb) должен показывать 17

Printf показывает только целые числа, так как% d является целым числом, а переменная n объявляется как целое число. sizeof (BUF), должно быть 8

Таким образом, 17, деленное на 8 (17/8), должно вывести 2, так как n объявлено как целое число. Поскольку memcpy использовался для копирования данных в s, а не в sb, я бы предположил, что это связано с выравниванием памяти; при условии, что это 64-битный компьютер, тогда на одном адресе памяти может быть 8 символов.

Например, давайте предположим, что кто-то вызвал malloc (1), чем следующие «свободные места» не выровнены ...

Второй вызов strlen, показывает правильный номер, так как копия строки была сделана для структуры s вместо sb

user413990
источник