Несподівана оптимізація strlen при зведенні 2-d масиву


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
Довідка: Мої звіти про 7cc.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, де компілятор не може цього знати. Це помилка компілятора .
Але

Відповіді:


-1

Я можу побачити деякі проблеми, і на них може вплинути те, як компілятор вирішить розмістити пам'ять.

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

У наведеному вище коді s.bє 23 вхідний масив масиву з 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);

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


-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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.