Używa malloc do niezdefiniowanego zachowania int do C ++ 20

95

Powiedziano mi, że następujący kod ma niezdefiniowane zachowanie do C ++ 20:

int *p = (int*)malloc(sizeof(int));
*p = 10;

Czy to prawda?

Argumentem było to, że czas życia intobiektu nie jest uruchamiany przed przypisaniem mu wartości ( P0593R6 ). Aby rozwiązać problem, newnależy użyć umieszczenia:

int *p = (int*)malloc(sizeof(int));
new (p) int;
*p = 10;

Czy naprawdę musimy wywoływać domyślny konstruktor, który jest trywialny, aby rozpocząć cykl życia obiektu?

Jednocześnie kod nie ma nieokreślonego zachowania w czystym C.Ale co jeśli przydzielę intkod w C i użyję go w kodzie C ++?

// C source code:
int *alloc_int(void)
{
    int *p = (int*)malloc(sizeof(int));
    *p = 10;
    return p;
}

// C++ source code:
extern "C" int *alloc_int(void);

auto p = alloc_int();
*p = 20;

Czy nadal jest to niezdefiniowane zachowanie?

anton_rh
źródło
8
Dla int? Nie. Dla std::string? Tak.
Eljay
8
@Eljay For int, też tak. Po prostu nie spowoduje to problemów w praktyce, jeśli tego nie zrobisz. Bo std::stringto oczywiście spowoduje problemy.
Barry
Przed C ++ 20 możesz dodać nowe miejsce docelowe. Byłby wtedy dobrze uformowany i prawdopodobnie nic by nie kosztował.
François Andrieux
8
Jakie nowe reguły w C ++ 20 to zmieniają?
Kevin
4
Nie powinno być int *p = (int*)malloc(sizeof(int)); p = new(p) int;? Kiedyś zdałem sobie sprawę, że nieprzypisanie wyniku umieszczenia nowego może również spowodować fatalne skutki (choć może to wyglądać trochę głupio).
Scheff

Odpowiedzi:

61

Czy to prawda?

Tak. Technicznie rzecz biorąc, żadna część:

int *p = (int*)malloc(sizeof(int));

w rzeczywistości tworzy obiekt typu int, więc dereferencja pjest UB, ponieważ nie ma inttam rzeczywistego .

Czy naprawdę musimy wywoływać domyślny konstruktor, który jest trywialny, aby rozpocząć życie obiektu?

Czy musisz postępować zgodnie z modelem obiektowym C ++, aby uniknąć niezdefiniowanego zachowania przed C ++ 20? Tak. Czy jakikolwiek kompilator rzeczywiście spowoduje szkodę, jeśli tego nie zrobisz? Nie, że jestem świadomy.

[…] Czy nadal jest to niezdefiniowane zachowanie?

Tak. Przed C ++ 20 nadal intnigdzie nie utworzyłeś obiektu, więc to jest UB.

Barry
źródło
Komentarze nie służą do rozszerzonej dyskusji; ta rozmowa została przeniesiona do czatu .
Makyen
Dlaczego język w timsong-cpp.github.io/cppwp/n3337/basic.life#1.1 nie jest wystarczający, aby nie był to UB? Przecież intw przykładzie uzyskano przechowywanie odpowiedniego rozmiaru i wyrównania - inttam zaczyna się żywotność obiektu.
avakar
41

Tak, to był UB. Lista sposobów istnienia intpuszki została wyliczona i żadna z nich nie ma zastosowania, chyba że uznasz, że malloc jest bezprzyczynowy.

Powszechnie uważano to za lukę w standardzie, ale jedną o małym znaczeniu, ponieważ optymalizacje wykonane przez kompilatory C ++ wokół tego konkretnego bitu UB nie spowodowały problemów z tym przypadkiem użycia.

Jeśli chodzi o drugie pytanie, C ++ nie narzuca sposobu interakcji C ++ i C. Zatem cała interakcja z C to ... UB, czyli zachowanie niezdefiniowane w standardzie C ++.

Yakk - Adam Nevraumont
źródło
5
Czy możesz rozwinąć wyliczoną listę sposobów zaistnienia int? Pamiętam, że zadałem podobne pytanie o długość życia typów prymitywnych i powiedziano mi, że prymityw może „istnieć” po prostu mówiąc, że istnieje, ponieważ specyfikacja nie mówi inaczej. Wygląda na to, że przegapiłem użyteczną sekcję specyfikacji! Chciałbym wiedzieć, którą sekcję powinienem był przeczytać!
Cort Ammon
7
@CortAmmon Wyliczona lista sposobów istnienia obiektu (dowolnego typu) w C ++ 20 znajduje się w [intro.object] : (1) z definicji (2) przez nowe wyrażenie (3) niejawnie według nowych reguł w P0593 (4) zmiana aktywnego członka związku (5) tymczasowo. (3) jest nowy w C ++ 20, (4) jest nowy w C ++ 17.
Barry
3
Czy interakcja C / C ++ to naprawdę UB? Bardziej sensowne byłoby zdefiniowanie jako implementacji, a nie niezdefiniowanie, w przeciwnym razie dziwne byłoby w ogóle mieć extern "C"w ogóle składnię.
Ruslan
4
@Ruslan: Implementacje mogą definiować dowolne zachowanie ISO C ++ pozostawia niezdefiniowane. (Na przykład gcc -fno-strict-aliasinglub domyślnie MSVC). Powiedzenie „zdefiniowana implementacja” wymagałoby od wszystkich implementacji C ++ zdefiniowania sposobu, w jaki współdziałają z niektórymi implementacjami C, więc sensowne jest pozostawienie w pełni implementacji, czy chcą coś takiego zrobić, czy nie.
Peter Cordes
4
@PeterCordes: Zastanawiam się, dlaczego tak wielu ludzi nie rozpoznaje różnicy między IDB a UB i przyjmuje jakieś dziwaczne przekonanie, że brak nakazu standardu, aby wszystkie implementacje przetwarzały konstrukcję, w znaczący sposób implikuje osąd, że nie należy się tego spodziewać po żadnej implementacji, a implementacje, które tego nie robią, nie mogą w konsekwencji być postrzegane jako gorsze.
supercat