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 -O0
tych wyników, 0 2
gdy się spodziewałem 2 2
. Kompilator zdecydował, że strlen
jest 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 X
kod (char *)&X
powinien generować wskaźnik, który może się iterować w całym X
- ta koncepcja powinna obowiązywać, nawet jeśli X
zdarzy się, że ma pod-tablice jako struktura wewnętrzna.
(Pytanie dodatkowe, czy istnieje flaga gcc, która wyłącza tę konkretną optymalizację?)
2 2
w różnych opcjach.s.b
jest ograniczony dob[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 przypadkachOdpowiedzi:
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.
W powyższym kodzie
s.b
znajduje się tablica z 23 pozycjami zawierająca 8 znaków. Gdy odnosisz się do tegos.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 lokalnejstrlen
.Masz 2 możliwe rozwiązania. Oni są:
lub
Próbowałem również uruchomić program i zademonstrować problem, ale zarówno clang, jak i wersja gcc, którą mam, z dowolnymi
-O
opcjami 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).źródło
W kodzie występują błędy.
na przykład jest ryzykowny, nawet jeśli s zaczyna się od b, powinno być:
Druga strlen () ma również błędy
na przykład powinno być:
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
źródło