2012-04-24 13 views
7

może wydaje się być głupie pytanie, ale naprawdę muszę to wyjaśnić:C++ const cast, nie wiem, czy to jest bezpieczne

Spowoduje to żadnego niebezpieczeństwa dla mojego programu?

Czy const_cast jest jeszcze potrzebny?

Jeśli zmienię wartości wskaźników wejściowych w miejscu, czy będzie on działał bezpiecznie z std::string, czy utworzy niezdefiniowane zachowanie?

Do tej pory jedyną obawą jest to, że może to wpłynąć na ciąg "some_text" za każdym razem, gdy zmienię wskaźnik wejściowy i sprawię, że będzie on niezdatny do użytku.

std::string some_text = "Text with some input"; 

char * input = const_cast<char*>(some_text.c_str()); 

Dzięki za danie mi kilka wskazówek, chciałbym uniknąć strzelać w moim stóp

+0

Ogólnie rzecz biorąc, ponieważ 'const_cast' usuwa bezpieczeństwo, powinieneś unikać go tak bardzo, jak tylko możesz. – Mesop

+0

Dzięki za wkład każdego ciała rozumiem teraz, że powinienem tego uniknąć, aby zachować bezpieczeństwo –

Odpowiedz

13

Jako przykład złego zachowania: interakcja z implementacją Copy On Write gcc.

#include <string> 
#include <iostream> 

int main() { 
    std::string const original = "Hello, World!"; 
    std::string copy = original; 

    char* c = const_cast<char*>(copy.c_str()); 
    c[0] = 'J'; 

    std::cout << original << "\n"; 
} 

W akcji pod numerem ideone.

Jello, World!

Problem? Jak sama nazwa wskazuje, implementacja gcc std::string wykorzystuje liczony wspólnie bufor refencjonowany pod osłoną. Kiedy łańcuch znaków zostanie zmodyfikowany, implementacja sprawnie sprawdzi, czy bufor jest obecnie udostępniony, a jeśli tak jest, skopiuj go przed modyfikacją, upewniając się, że nowy napis nie wpływa na inne ciągi współdzielące ten bufor (stąd nazwa, kopiuj przy zapisie).

Teraz, dzięki złemu programowi, uzyskujesz dostęp do współużytkowanego bufora za pomocą metody const (obiecując, że niczego nie zmienisz), ale zmienisz to!

Należy zauważyć, że przy implementacji MSVC, która nie korzysta z funkcji kopiowania przy zapisie, zachowanie będzie inne ("Hello, World!" zostanie wydrukowane poprawnie).

To jest dokładnie esencja Niezdefiniowane zachowanie.

+16

Esencja * Niezdefiniowanego zachowania *: Twój świat zmienia się w _jello_. –

+0

Dzięki za ten wgląd –

5

zmodyfikować nieodłącznie const obiekt oddając dala jej constness użyciu const_cast jest Undefined Behavior.

string::c_str() zwraca const char *, tj: wskaźnik do stałej c stylu ciąg. Technicznie, modyfikowanie tego spowoduje nieokreślone zachowanie.

Należy zauważyć, że użycie const_cast ma miejsce, gdy masz wskaźnik const dla danych niestałych i chcesz zmodyfikować dane niestałe.

+0

Nie znalazłem opcji łańcuchowej, która wykonuje operację c_str() bez stałej, czy istnieje alternatywa, która jest preferowana? –

+0

Może się zdarzyć, że 'c_str' zwróci wskaźnik do tablicy' char', a nie 'const char'. Tak więc formalnie można powiedzieć, że nie można zagwarantować, że program jest poprawny, jeśli modyfikuje obiekty poprzez 'input', a nie gwarantuje, że program jest nieprawidłowy. –

+0

@OliverStutz: Musisz mieć inną odpowiednio przydzieloną (tablicę lub wskaźnik) zmienną, skopiuj do niej ciąg, a następnie zmodyfikuj ciąg skopiowany do innej zmiennej. –

1

Tak, przyniesie to zagrożenie, ponieważ

  1. input punktów, aby cokolwiek c_str dzieje się teraz, ale jeśli some_text kiedykolwiek zmiany lub odchodzi, będziesz lewej wskaźnik, który wskazuje na śmieci . Wartość c_str jest gwarantowana tylko wtedy, gdy ciąg nie ulega zmianie. A nawet formalnie, tylko jeśli nie wywołasz też c_str() w innych ciągach.
  2. Dlaczego trzeba odrzucić const? Nie zamierzasz pisać do *input, prawda? To nie nie!
+0

To było dokładnie to, co chciałem zrobić, aby zmodyfikować ciąg znaków (np. Usunąć zduplikowane postacie), moim największym problemem było to, że faktycznie pozwala mi to skompilować i uruchomić, ale właśnie to spowodowało, że otworzyłem ten post, ponieważ wydaje się, że nie można go obsłużyć the const away, a potem jestem w stanie go poprawnie napisać i zmodyfikować (prawdopodobnie już idzie na południe, ale nie jest dla mnie widoczny) –

+1

@OliverStutz Rzeczy takie jak usuwanie duplikatów można zrobić, wywołując wbudowane 'std :: string' Funkcje. Ale jeśli podoba ci się stare C działa lepiej, idź starym C do końca i najpierw wykonaj 'strcpy'! –

2

std::string zarządza własną pamięć wewnętrzna, dlatego też zwraca wskaźnik do tej pamięci bezpośrednio, jak to robi z funkcją c_str(). Upewnia się, że jest stały, aby Twój kompilator ostrzegał Cię, jeśli spróbujesz to zmodyfikować.

Użycie const_cast w ten sposób dosłownie odrzuca takie bezpieczeństwo i jest tylko możliwą do zaakceptowania praktyką, jeśli jesteś absolutnie pewny, że pamięć nie zostanie zmodyfikowana.

Jeśli nie możesz tego zagwarantować, musisz skopiować ciąg i użyć kopii .; Z pewnością bezpieczniej jest to zrobić w każdym przypadku (można użyć strcpy).

2

Zobacz C++ reference www:

const char* c_str () const; 

„generuje sekwencję NUL znaków (C-string) o tej samej treści, co obiekt String i zwraca go jako wskaźnik do tablicy znaków.

Końcowy pusty znak jest automatycznie dołączany.

Zwrócona tablica wskazuje na wewnętrzną lokalizację z wymaganą przestrzenią dyskową dla tej sekwencji znaków plus jej końcowy pusty znak, ale wartości w tej tablicy nie powinny być modyfikowane w programie i są gwarantowane, że pozostaną niezmienione do momentu następne wywołanie non-stałej funkcji składowej obiektu strun.”

4

Wystarczy odlewania nie przyniesie niezdefiniowanej zachowanie. Modyfikowanie danych wskazał jednak będzie. (Also see ISO 14882:98 5.2.7-7).

Jeśli chcesz wskaźnik do modyfikowalnych danych, możesz mieć

std::vector<char> wtf(str.begin(), str.end()); 
char* lol= &wtf[0]; 
+7

wtf & lol - nowy foo & bar? – gwiazdorrr

+1

Tyle tylko, że teraz masz ciąg kończący się na zero. których nie masz z 'char * c = str.c_str(); std :: vector foo (c, c + str.size() + 1); ' – stefaanv

1

To bardzo źle. Sprawdź, co std :: string :: c_str() does i zgadzam się ze mną.

Po drugie, zastanów się, dlaczego nie chcesz mieć stałego dostępu do elementów wewnętrznych std :: string. Wygląda na to, że chcesz zmodyfikować zawartość, ponieważ w przeciwnym razie użyłbyś wskaźnika const char. Obawiasz się również, że nie chcesz zmieniać oryginalnego ciągu znaków. Dlaczego nie napisać

std::string input(some_text); 

Wtedy masz std :: string, że można zadzierać bez wpływu na oryginalny i masz std :: funkcjonalność ciąg zamiast pracować z surowego wskaźnik C++ ...

+0

Jeśli OP potrzebuje' char * ', robienie tego w ten sposób nie jest dobre, ponieważ masz dokładnie takie same problemy z nowym ciąg jak z oryginalnym! –

1

Innym rozwiązaniem jest to, że kod jest niezwykle trudny w utrzymaniu. Przykład: kilka lat temu musiałem zmienić kod zawierający długie funkcje. Autor napisał napisy funkcji, aby zaakceptować parametry const, ale potem był const_cast ich w funkcji, aby usunąć constness. Złamało to dorozumianą gwarancję udzieloną przez funkcję i sprawiło, że bardzo trudno było ustalić, czy parametr zmienił się, czy też nie, w pozostałej części kodu.

Krótko mówiąc, jeśli masz kontrolę nad ciągiem znaków i uważasz, że musisz go zmienić, najpierw ustaw go jako niestały. Jeśli tego nie zrobisz, będziesz musiał wziąć kopię i pracować z tym.

1

to UB. Na przykład, można zrobić coś takiego to:

size_t const size = (sizeof(int) == 4 ? 1024 : 2048); 
int arr[size]; 

bez obsady i comiler nie zgłosi błąd. Ale ten kod jest nielegalny. Morale polega na tym, że za każdym razem trzeba rozważyć działanie.

Powiązane problemy