2012-04-22 13 views
13

Wiem, że na przykład "hello" jest typu const char*. Więc moje pytania to:Dlaczego jest możliwe przypisanie znaku stałego * do znaku *?

  1. Jak możemy przypisać dosłownego ciąg jak "hello" do niebędącego const char* tak:

    char* s = "hello"; // "hello" is type of const char* and s is char* 
            // and we know that conversion from const char* to 
            // char* is invalid 
    
  2. jest ciągiem znaków jak "hello", która odbędzie się w pamięci wszystkich mój program, czy jest to po prostu zmienna tymczasowa, która zostanie zniszczona po zakończeniu wypowiedzi?

+2

Jest to dozwolone tylko w przypadku zgodności z C. Zwykle w C++ jest uważany za przestarzały, a dobre kompilatory ostrzegają. – iammilind

Odpowiedz

22

W rzeczywistości "hello" jest typu char const[6].

Ale sedno pytania jest nadal słuszne - dlaczego C++ pozwala nam przypisać lokalizację pamięci tylko do odczytu do typu innego niż const?

Jedynym powodem tego jest kompatybilność wsteczna ze starym kodem C, która nie znała const. Jeśli C++ byłby tutaj ścisły, zepsułoby to wiele istniejącego kodu.

To powiedziawszy, większość kompilatorów może być skonfigurowana do ostrzec o takim kodzie jako przestarzałe, a nawet zrobić to domyślnie. Co więcej, C++ 11 nie dopuszcza tego całkowicie, ale kompilatory mogą go jeszcze nie wymuszać.


Na Standerdese Wentylatory:
[Nr 1]C++ 03 standard: §4.2/2

Łańcuch znaków (2.13.4), która jest szeroka literał łańcuchowy można przekonwertować na wartość r type "wskaźnik do char"; szeroki ciąg literału można przekształcić na wartość typu "wskaźnik do wchar_t". W obu przypadkach wynikiem jest wskaźnik do pierwszego elementu tablicy. Ta konwersja jest rozważana tylko wtedy, gdy istnieje wyraźny odpowiedni docelowy typ wskaźnika, a nie wtedy, gdy istnieje ogólna potrzeba konwersji z lwartości do wartości r. [Uwaga: ta konwersja jest przestarzała.. Patrz załącznik D.] W celu uszeregowania w rozdzielczości przeciążenia (13.3.3.1.1), konwersja ta jest uważana za konwersję między tablicami a po konwersji kwalifikacji (4.4). [Przykład: "abc" jest konwertowane na "wskaźnik na stały znak" jako konwersja tablica do wskaźnika, a następnie na "wskaźnik do znaku" jako konwersja kwalifikacji. ]

C++ 11 po prostu usuwa powyższy cytat, co oznacza, że ​​jest to nielegalny kod w C++ 11.

[Nr 2]C99 Standard 6.4.5/5 "string literale - semantyka":

w fazie translacji 7 bajt lub kod wartości zerowej jest dołączony do każdego znaku wielobajtowym sekwencja, która wynika z literału lub literałów ciągu. Wielobajtowa sekwencja znaków jest następnie wykorzystywana do zainicjowania tablicy czasu i długości statycznego przechowywania wystarczającej do zachowania sekwencji.W przypadku literałów łańcuchowych znaków elementy tablicy mają typ char i są inicjowane pojedynczymi bajtami wielobajtowej sekwencji znaków; w przypadku literałów o szerokich ciągach elementy tablicy mają typ wchar_t i są inicjowane sekwencją szerokich znaków ...

Nie jest określone, czy te tablice są odrębne, pod warunkiem, że ich elementy mają odpowiednie wartości. Jeśli program próbuje zmodyfikować taką tablicę, zachowanie jest niezdefiniowane.

+1

Dodano odpowiednie cytaty ze Standardu. Mam nadzieję, że nie będziesz miał nic przeciwko temu. –

+0

MSVC w trybie debugowania uniemożliwi zapisanie do literału łańcuchowego w czasie wykonywania, GCC w czasie kompilacji (jeśli to możliwe). Istnieje jednak opcja, uważam, że jest to -wwrite-stringi lub takie, które włącza tryb kompatybilności i umieszcza literały łańcuchowe w pamięci R/W. –

+2

@Als Zrobiłeś prostotę i uczyniłeś ją brzydką. ;-) Ale dzięki.Tak naprawdę chciałem to dodać, ale w ogóle nie mogę znaleźć odpowiednich fragmentów w mojej wersji roboczej C++ 11. 2.14.3 określa typ, a załącznik określa nieważność konwersji, ale nic nie wskazuje na to, że jest to przestarzałe, ale ważne w C++ 03. –

1

prostu użyć string:

std::string s("hello"); 

To byłoby C++ sposób. Jeśli naprawdę musisz użyć char, musisz utworzyć tablicę i skopiować jej zawartość.

2

jest ciągiem znaków jak „cześć” odbędzie pamięci w całym moim programie wszystko to jak tymczasowej zmiennej, która będzie się zniszczone, gdy oświadczenie kończy.

Jest przechowywany w danych programowych, więc jest możliwy do zrealizowania przez cały okres istnienia programu. Możesz zwrócić wskaźniki i odniesienia do tych danych z bieżącego zakresu.

Jedynym powodem, dla którego const char* jest rzutowany na char*, jest zgodność z c, podobnie jak wywołania systemowe winapi. I ta obsada nie jest niczym niezwykłym, jak żaden inny rzut konstelacji.

+0

tak const char * jest jedynym typem, który może zostać rzucony na char * w sposób nieoczekiwany przez kompilator.? – AlexDan

+0

AlexDan, 'const char *' jest jedynym typem, który może być rzutowany na jego nie stałą formę (na przykład 'const int *' nie może być rzutowane na 'int *' w nieodwołalnie). Ale możesz dostosować kompilator, aby wygenerować waring. –

+2

również, * cast * jest nieregularnym czasownikiem, 3 formy: * cast-cast-cast * –

-3

W twoim przykładzie nie przypisujesz, ale konstruujesz. std :: string, na przykład, ma konstruktor std::string(const char *) (w rzeczywistości jest bardziej skomplikowany, ale nie ma znaczenia). Podobnie, char * (jeśli był typem, a nie wskaźnikiem do typu), mógłby mieć konstruktor const char *, który kopiuje pamięć.

ja właściwie nie wiem, jak naprawdę działa kompilator tutaj, ale myślę, że to może być podobne do tego, co już opisano powyżej: kopia "Hello" jest zbudowany w stosie i s jest inicjowany z adresem Ta kopia jest.

+2

To po prostu nie jest poprawne. Kopia nie jest tworzona, a modyfikowanie łańcucha za pomocą wskaźnika to UB. –

+0

Na stosie nie ma kopii "Hello"! Jest to literał łańcuchowy, który zostanie utworzony w jakimś globalnym segmencie danych (https://secure.wikimedia.org/wikipedia/en/wiki/Data_segment) (ale jest to szczegół szczegółowy dotyczący implementacji). – Praetorian

+0

@KonradRudolph, nigdy nie powiedziałem, że to było poprawne. Smutno mi, że może tak być. Dzięki za linki. – Steed

1

Odpowiedź na drugie pytanie jest taka, że ​​zmienna s jest przechowywana w pamięci RAM jako typ wskaźnika do znaku. Jeśli jest globalny lub statyczny, jest przydzielany na stercie i pozostaje tam przez cały czas działania uruchomionego programu. Jeśli jest to zmienna lokalna ("auto"), jest ona przydzielana na stosie i pozostaje tam do czasu powrotu bieżącej funkcji. W obu przypadkach zajmuje ilość pamięci wymaganą do utrzymania wskaźnika.

Ciąg "Hello" jest stałą i jest przechowywany jako część samego programu, wraz ze wszystkimi pozostałymi stałymi i inicjalizującymi. Jeśli zbudowałeś swój program do działania na urządzeniu, łańcuch byłby przechowywany w pamięci ROM.

Należy zauważyć, że ponieważ ciąg znaków jest stały i s jest wskaźnikiem, kopiowanie nie jest konieczne. Wskaźnik s po prostu wskazuje na miejsce, w którym zapisany jest ciąg.

Powiązane problemy