Konwersja do void ** w różnych kompilatorach

9

Uruchomiłem następujący kod za pośrednictwem różnych kompilatorów:

int main()
{
    float **a;
    void **b;
    b = a;
}

Z tego co udało mi się zebrać, void **to nie ogólna wskazówka co oznacza, że każdy z innego wskaźnika konwersji nie powinien skompilować lub przynajmniej rzucić ostrzeżenie. Oto moje wyniki (wszystkie wykonane w systemie Windows):

  • gcc - Zgłasza ostrzeżenie, zgodnie z oczekiwaniami.
  • g ++ - Zgłasza błąd, zgodnie z oczekiwaniami (wynika to z mniej dopuszczalnego pisania w C ++, prawda?)
  • MSVC (cl.exe) - nie generuje żadnych ostrzeżeń, nawet jeśli określono / Wall.

Moje pytanie brzmi: czy brakuje mi czegoś w tym wszystkim i czy jest jakiś konkretny powód, dla którego MSVC nie wyświetla ostrzeżenia? MSVC nie produkują ostrzeżenie podczas konwersji od void ** do float **.

Kolejna uwaga: jeśli zastąpię a = bjawną konwersją a = (void **)b, żaden z kompilatorów nie wyświetli ostrzeżenia. Myślałem, że to powinna być niepoprawna obsada, więc dlaczego nie byłoby żadnych ostrzeżeń?

Zadaję to pytanie, ponieważ zacząłem się uczyć CUDA oraz w oficjalnym Przewodniku programowania ( https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#device-memory ) można znaleźć następujący kod:

// Allocate vectors in device memory
float* d_A;
cudaMalloc(&d_A, size);

który powinien wykonać niejawną konwersję na void **for &d_A, ponieważ pierwszy argument cudaMallocjest typu void **. Podobny kod można znaleźć w całej dokumentacji. Czy to po prostu niechlujna praca na końcu NVIDII, czy znowu coś mi brakuje? Ponieważ nvccużywa MSVC, kod kompiluje się bez ostrzeżeń.

CaptainProton42
źródło
3
Błędy z opublikowanego 3 live: godbolt.org/z/GQWMNo
Richard Critten
3
Błędy kodu dla mnie w MSVC. Jakiej wersji używasz? Ale tak, void**to nie jest ogólny wskaźnik. Tylko void*jest.
NathanOliver
Dzięki za szybką odpowiedź! 19.24.28315 dla x64 najwyraźniej? Tak naprawdę nigdy wcześniej nie korzystałem z MSVC.
CaptainProton42
2
(void**)jest jawną obsadą w stylu C. Mówi kompilatorowi, aby nie przyglądał się dokładnie temu, co robisz, i żeby ci zaufał. Jest to wyraźne zastąpienie systemu bezpieczeństwa typu, a kompilatory są zobowiązane do zaakceptowania praktycznie dowolnego rodzaju konwersji. Należy unikać rzutów w stylu C, są one zbyt mocne. Użyj rzutowań C ++, static_castktóre będą narzekać, jeśli spróbujesz zrobić coś, co nie ma sensu.
François Andrieux
@RichardCritten zły język - bez błędów godbolt.org/z/RmFpgN C ++ cuda jak zwykle wymaga jawnych rzutów.
P__J__

Odpowiedzi:

4

Czy brakuje mi czegoś w tym wszystkim i czy jest jakiś konkretny powód, dla którego MSVC nie wyświetla ostrzeżenia? MSVC wyświetla ostrzeżenie podczas konwersji z void ** na float **

To przypisanie bez rzutowania stanowi naruszenie ograniczenia, więc standardowy zgodny kompilator wyświetli ostrzeżenie lub błąd. Jednak MSVC nie jest w pełni zgodny z implementacją C.

Kolejna uwaga: jeśli zastąpię a = b jawną konwersją a = (void **) b, żaden z kompilatorów nie wyśle ​​ostrzeżenia. Myślałem, że to powinna być niepoprawna obsada, więc dlaczego nie byłoby żadnych ostrzeżeń?

W niektórych sytuacjach konwersje wskaźnika za pomocą rzutowania są dozwolone. Norma C mówi w sekcji 6.3.2.3p7, co następuje:

Wskaźnik do typu obiektu można przekonwertować na wskaźnik do innego typu obiektu. Jeśli wynikowy wskaźnik nie jest poprawnie wyrównany dla typu odwołania, zachowanie jest niezdefiniowane. W przeciwnym razie, po ponownej konwersji wynik będzie porównywany z oryginalnym wskaźnikiem. Kiedy wskaźnik do obiektu jest konwertowany na wskaźnik do typu znaku, wynik wskazuje na najniższy adresowany bajt obiektu. Kolejne przyrosty wyniku, aż do wielkości obiektu, dają wskaźniki do pozostałych bajtów obiektu.

Więc może konwertować pomiędzy typami wskaźnik ile istnieją żadne problemy wyrównania i tylko przekonwertować z powrotem (chyba że celem jest char *).

float* d_A;
cudaMalloc(&d_A, size);

...

Czy to po prostu niechlujna praca na końcu NVIDII, czy znowu coś mi brakuje?

Przypuszczalnie ta funkcja wykasowuje dany wskaźnik i zapisuje adres pewnej przydzielonej pamięci. To by znaczyło, że próbuje napisać do float *tak, jakby to był void *. To nie to samo, co typowa konwersja do / z void *. Ściśle mówiąc, wygląda to na niezdefiniowane zachowanie, chociaż „działa”, ponieważ nowoczesne procesory x86 (gdy nie są w trybie rzeczywistym) używają tej samej reprezentacji dla wszystkich typów wskaźników.

dbush
źródło
@dbush Bardzo pouczające, dziękuję! Rozumiem, dlaczego to działa, jeśli działa. Czy jednak większość kompilatorów nie zgłasza ostrzeżenia, a nawet błędu, ponieważ &d_Anie ma wymaganego typu?
CaptainProton42
3
Uważaj, jak to interpretujesz. Pomiędzy kompilacją CUDA za pomocą kompilatora C ++ można zastosować sztuczki szablonowe
talonmies