Nieoczekiwana optymalizacja strlen podczas aliasingu tablicy 2-d

28

Oto mój kod:

#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);
}

Używanie gcc 8.3.0 lub 8.2.1 z dowolnym poziomem optymalizacji, z wyjątkiem -O0tych wyników, 0 2gdy się spodziewałem 2 2. Kompilator zdecydował, że strlenjest on ograniczony b[0]i dlatego nigdy nie może być równy ani przekraczać dzielonej wartości.

Czy to błąd w moim kodzie czy błąd w kompilatorze?

Nie jest to wyraźnie określone w standardzie, ale myślałem, że głównym interpretacją pochodzenia wskaźnika jest to, że dla każdego obiektu Xkod (char *)&Xpowinien generować wskaźnik, który może się iterować w całym X- ta koncepcja powinna obowiązywać, nawet jeśli Xzdarzy się, że ma pod-tablice jako struktura wewnętrzna.

(Pytanie dodatkowe, czy istnieje flaga gcc, która wyłącza tę konkretną optymalizację?)

MM
źródło
4
Ref: Moje raporty gcc 7.4.0 2 2w różnych opcjach.
chux - Przywróć Monikę
2
@Wszystkie standardowe gwarancje, że są pod tym samym adresem (struct nie może mieć wstępnego wypełnienia)
MM
3
@ DavidRankin-ReinstateMonica ”, w wyniku czego granice char (*) [8] są ograniczone do b [0]. Ale o tyle, o ile mi wiadomo,„ myślę, że to pasuje. ponieważ s.bjest ograniczony do b[0]tego, jest ograniczony do 8 znaków, a zatem dwie opcje: (1) dostęp poza granicami, w przypadku gdy istnieje 8 znaków innych niż null, czyli UB, (2) występuje znak null, w którym długość jest mniejsza niż 8, a zatem podzielenie przez 8 daje zero. Tak więc zestawienie kompilatora (1) + (2) może użyć UB, aby dać ten sam wynik w obu przypadkach
user2162550
3
Biorąc pod uwagę, że & s == & s.b, nie ma możliwości, aby wynik mógł się różnić. Jak pokazał @ user2162550, strlen () nie jest wywoływany, a kompilator zgaduje, jaki może być jego wynik, nawet w przypadku godbolt.org/z/dMcrdy, w którym kompilator go nie zna. Jest to błąd kompilatora .
Ale

Odpowiedzi:

-1

Są pewne problemy, które widzę i na które może wpływać sposób, w jaki kompilator decyduje się na pamięć układu.

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

W powyższym kodzie s.bznajduje się tablica z 23 pozycjami zawierająca 8 znaków. Gdy odnosisz się do tego s.b, otrzymujesz adres pierwszego wpisu w tablicy 23 bajtów (i pierwszego bajtu w tablicy 8 znaków). Kiedy kod mówi &s.b, pyta o adres adresu tablicy. Pod przykrywkami kompilator najprawdopodobniej generuje trochę pamięci lokalnej, przechowując tam adres tablicy i podając adres pamięci lokalnej strlen.

Masz 2 możliwe rozwiązania. Oni są:

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

lub

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

Próbowałem również uruchomić program i zademonstrować problem, ale zarówno clang, jak i wersja gcc, którą mam, z dowolnymi -Oopcjami nadal działała zgodnie z oczekiwaniami. Dla tego, co warto, korzystam z wersji clang 9.0.0-2 i gcc w wersji 9.2.1 na x86_64-pc-linux-gnu).

JonBelanger
źródło
-2

W kodzie występują błędy.

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

na przykład jest ryzykowny, nawet jeśli s zaczyna się od b, powinno być:

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

Druga strlen () ma również błędy

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

na przykład powinno być:

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

Ciąg sb, jeśli jest poprawnie skopiowany, powinien mieć 17 liter. Nie jestem pewien, jak struktury są przechowywane w pamięci, jeśli są wyrównane. Czy sprawdziłeś, czy ktoś zawiera skopiowane 17 znaków?

Strlen (sb) powinien więc pokazywać 17

Printf pokazuje tylko liczby całkowite, ponieważ% d jest liczbą całkowitą, a zmienna n jest deklarowana jako liczba całkowita. sizeof (BUF), powinien wynosić 8

Zatem 17 podzielone przez 8 (17/8) powinno wypisać 2, ponieważ n jest zadeklarowane jako liczba całkowita. Ponieważ memcpy było używane do kopiowania danych do s, a nie do sb, zgaduję, że ponieważ ma to związek z wyrównaniem pamięci; zakładając, że jest to komputer 64-bitowy, na jednym adresie pamięci może znajdować się 8 znaków.

Załóżmy na przykład, że ktoś wywołał malloc (1), a następne „wolne miejsce” nie jest wyrównane ...

Drugie wywołanie strlen pokazuje poprawny numer, ponieważ kopia łańcucha została wykonana w strukturze s zamiast w sb

użytkownik413990
źródło