C++ - const char *napis - czyli jak to w rzeczywistości działa

0

wiem, że to są podstawy i spotykam się z mnóstwem zarzutów że ich nie znam - fakt, nie wszystko wiem ale ciekaw jestem jak bardzo dobrze wyjaśnicie mi to zagadnienie

jest sobie taki kawałek kodu

const char *string = "to jest dlugi string";

Domyślam się, że kompilator za programistę wykrywa znak '\0' (NULL) albo sam go tam wstawia i na tej podstawie określa koniec tego stringu

tylko pytanie jest takie, jak w rzeczywistości "za kulisami" to wszystko się odbywa ? Co się tam dzieje ? Jak char który przechowuje JEDEN znak "wie" że to jest w uj długi ciąg znaków i skąd wie kiedy się kończy ? Czy mógłby ktoś to wyjaśnić i podać jakiś kod źródłowy ? Nie spotkałem się z opisem tego zagadnienia w książkach dla początkujących

PS. wiem, że można to wypisać za pomocą pętli ale celowo omijam w tyn pytaniu pętlę

4

Zauważ, że typ tej zmiennej to wskaźnik char*, a nie char. Wskaźnik nie ma pojęcia ile elementów ma ciąg znaków, trzeba przejść bajt po bajcie w poszukiwaniu bajtu \0, żeby się tego dowiedzieć. Ta wartość prawdopodobnie zostanie wpisana bezpośrednio w sekcji .rodata (lub odpowiedniku), z zerowym bajtem na końcu. Sam wskaźnik prawdopodobnie zostanie też wpisany na sztywno w sekcji .data, o ile deklaracja jest poza funkcją. W przeciwnym wypadku, adres będzie wrzucony na stos.

1

char to jeden znak, ale char * to wskaźnik na tablicę znaków o dowolnej długości. Kompilator interpretuje "to jest dlugi string" jako konieczność zarezerwowania pamięci o 1 znak większej niż długość stringu i wstawienie \0 na końcu.

Jednakże użycie const sprawia, że ta tablica bajtowa jest częścią skompilowanego pliku, który jest ładowany do pamięci.

2

To powinno wyjaśnić:

#include <iostream>
using namespace std;

void func()
{
	const char *tmp="Ala ma kota";
	cout<<"func(): "<<(void*)tmp<<endl;
}

int main()
{
	func();
	const char *msg="Ala ma kota";
	cout<<"main(): "<<(void*)msg<<endl; // adres jest ten sam, czyli takie same stałe ładowane w jednym miejscu.
	msg="Ewa ma psa";
	cout<<"main(): "<<(void*)msg<<endl;
	return 0;
}

https://ideone.com/egkQs3

7

Każdy literał napisu jest typu: const char[długośćNapisu + 1].
Poza znakami zawierającymi sam napis, w ostatniej komórce domyślnie umieszczane jest zero oznaczające koniec napisu.
Teraz ta linijka:

const char *string = "to jest dlugi string";

zawiera niejawną konwersję z const char[] do const char *, bo w C wszystkie tablice domyślnie ulegają "rozpadowi" (decay) do wskaźnika.

Gdy używasz operatora strumienia na const char * operator<<(std::ostream&, const char *), strumień przekaże wszystkie znaki aż do napotkania zera, którego nie wypisze.

To ma kilka konsekwencji: https://godbolt.org/z/vMn1xefKa

#include <iostream>
#include <string>
#include <cstring>
#include <string_view>

using namespace std::literals;

int main()
{
    const char *a = "ala ma kota\0 albo psa";
    const char *b = a + std::strlen(a) + 1;
    const char nie_ma_zera[]{ 't', 'o', ' ', 'j', 'e', 's', 't', ' ', 'p', 'o', 'p', 's', 'u', 't', 'e'};

    std::cout << a << '\n';
    std::cout << b << '\n';
    std::cout << "śmieci na końcu: " << nie_ma_zera << '\n'; // Niezdefiniowane zachowanie 

    auto zero_w_srodku = "ala ma kota\0 albo psa"sv; // to jest std::string_view
    std::cout << zero_w_srodku.size() << " :" << zero_w_srodku << '\n';
    return 0;
}

Bardziej namieszane demo razem z użyciem std::string_view, którego nie musisz znać, ale potrafi ignorować zero końcowe.

I jeszcze demo, że narządzie "address sanitizer" wykrywa "niezdefiniowane zachowanie": https://godbolt.org/z/qq8o6vfKo

0

@AnyKtokolwiek: To mu napisz lepszy interpreter :P — PerlMonk 25 minut temu

W wątku brakuje stwierdzenie, że doklejanie jednego bajtu z wartością 0x00 nie jest w żadnej mierze związane z char[], char *, tylko z literałem w pazurkach (wyjątek od zasad generalnych)
Przypomnę, że WSZYSTKIE inne sposoby inicjowania tablic (integereami, realami - znakami też, w tym tym wypadku "normalny" sposób inicjowania) jest z przecinkiem = {a,b , c}

Czyli analiza tematu ma odbyć się w 2 częściach:

  • literał w pazurkach (będący syntaktycznym i semantycznym wyjątkiem z innych literałów)
  • konstrukcja językowa C, z jaką jest związany

Upd: Zapomniałem o autorze
@zkubinski dziel mądrze zagadnienia, aby się skupiać na wąskich, mogących otrzymać szybką i ścisła odpowiedź, a nie na wiaderkach z pomyja... konglomeratami problemów

0

@AnyKtokolwiek:

@zkubinski dziel mądrze zagadnienia, aby się skupiać na wąskich, mogących otrzymać szybką i ścisła odpowiedź, a nie na wiaderkach z pomyja... konglomeratami problemów

nie rozumiem ? źle zrobiłem, że założyłem wątek z tym problemem czy co ?

1

Dodam jeszcze,

Domyślam się, że kompilator za programistę wykrywa znak '\0' (NULL)

Znak \0 to nie jest NULL, ten drugi to jest (void*)0.

1 użytkowników online, w tym zalogowanych: 0, gości: 1