Dlaczego konwersja ze stałej ciągu na „char *” jest poprawna w C, ale nieprawidłowa w C ++

163

Standard C ++ 11 (ISO / IEC 14882: 2011) mówi w § C.1.1:

char* p = "abc"; // valid in C, invalid in C++

W przypadku C ++ jest to w porządku, ponieważ wskaźnik do literału ciągu jest szkodliwy, ponieważ każda próba jego modyfikacji prowadzi do awarii. Ale dlaczego jest ważny w C?

C ++ 11 mówi również:

char* p = (char*)"abc"; // OK: cast added

Co oznacza, że ​​jeśli rzut zostanie dodany do pierwszej instrukcji, stanie się ważny.

Dlaczego rzutowanie sprawia, że ​​druga instrukcja jest ważna w C ++ i czym różni się od pierwszej? Czy to nadal nie jest szkodliwe? Jeśli tak, to dlaczego norma mówi, że jest OK?

rullof
źródło
3
C ++ 11 nie pozwala na pierwszą. Nie mam pojęcia, dlaczego C stworzył typ literału łańcuchowego char[]w pierwszej kolejności. Drugi to const_castw przebraniu.
chris,
4
Jest po prostu zbyt wiele starszego kodu C, który mógłby się zepsuć, gdyby ta reguła została zmieniona.
Paul R
1
proszę zacytować tekst, w którym norma mówi, że druga jest OK.
Nawaz
13
Język C miał wcześniej literały łańcuchowe const, więc niekoniecznie były const.
Casey,
2
Języki C i C ++ umożliwiają rzutowanie z niemal dowolnego typu na inny. Nie oznacza to, że te odlewy są sensowne i bezpieczne.
Siyuan Ren

Odpowiedzi:

207

Aż do C ++ 03, twój pierwszy przykład był prawidłowy, ale używał przestarzałej niejawnej konwersji - literał ciągu powinien być traktowany jako typ char const *, ponieważ nie możesz modyfikować jego zawartości (bez powodowania niezdefiniowanego zachowania).

Począwszy od C ++ 11, niejawna konwersja, która była przestarzała, została oficjalnie usunięta, więc kod, który od niej zależy (jak Twój pierwszy przykład) nie powinien już się kompilować.

Zauważyłeś jeden sposób, aby zezwolić na kompilację kodu: chociaż niejawna konwersja została usunięta, jawna konwersja nadal działa, więc możesz dodać rzutowanie. Chciałbym nie , jednak należy wziąć pod uwagę tę „fixing” kod.

Prawidłowe naprawienie kodu wymaga zmiany typu wskaźnika na właściwy typ:

char const *p = "abc"; // valid and safe in either C or C++.

Co do tego, dlaczego było to dozwolone w C ++ (i nadal jest w C): po prostu dlatego, że istnieje wiele istniejącego kodu, który zależy od tej niejawnej konwersji, a złamanie tego kodu (przynajmniej bez oficjalnego ostrzeżenia) najwyraźniej wydawało się standardowym komitetom, takim jak zły pomysł.

Jerry Coffin
źródło
8
@rullof: Jest to na tyle niebezpieczne, że nie daje żadnej znaczącej elastyczności, przynajmniej w przypadku kodu, który dba (w ogóle) o przenośność. Pisanie do literału ciągu zazwyczaj powoduje przerwanie programu w nowoczesnym systemie operacyjnym, więc zezwolenie kodowi na (próbę) zapisu w tym miejscu nie dodaje żadnej znaczącej elastyczności.
Jerry Coffin
3
Fragment kodu podane w tej odpowiedzi char const *p = "abc";jest „prawidłowy i bezpieczny zarówno w C i C ++”, a nie „prawidłowy i bezpieczny w obu C lub C ++”.
Daniel Le,
4
@DanielLe oba te zdania mają to samo znaczenie
Caleth
3
O mój panie! [Wsuń mocno język w policzek] Przepraszam, ale „lub” to poprawny termin. Kod można skompilować jako C lub C ++, ale nie można go jednocześnie kompilować jako C i C ++. Możesz wybrać jedną z nich, ale musisz dokonać wyboru. Nie możesz mieć obu naraz. [wznowić normalną operację języka].
Jerry Coffin
2
Nie, oba / i są tutaj najwyraźniejszym i najbardziej poprawnym sformułowaniem. Albo / lub zdarza się, że przekazuje właściwe znaczenie, ale technicznie nie jest to tak jasne. Albo sam jest nieodwracalnie błędny ( A lub B nie równa się A i B ).
Apollys obsługuje Monikę
15

Jest ważny w C z powodów historycznych. C tradycyjnie określał, że typ literału ciągu to char *raczej niż const char *, chociaż kwalifikował go, mówiąc, że w rzeczywistości nie możesz go modyfikować.

Kiedy używasz rzutowania, zasadniczo mówisz kompilatorowi, że wiesz lepiej niż domyślne reguły dopasowywania typów, i sprawia, że ​​przypisanie jest OK.

Barmar
źródło
3
To był char[N]i został zmieniony na const char[N]. Zawiera informacje o rozmiarze.
chris
1
W języku C dosłowny ciąg znaków jest, char[N]ale nie jest char*np."abc"char[4]
Grijesh Chauhan
2

Możesz także użyć strdup :

char* p = strdup("abc");
baz
źródło