Pytanie - wskaźniki, operator wyłuskania i inicjowanie

0

Cześć, mam takie małe pytanko. Dlaczego taki zapis nie jest poprawny:

char *zmienna;
cin >> zmienna;

Czy chodzi o to, że program nie wie, ile przydzielić pamięci pod zmienną?

0

Można bo czemu by nie, ale to chyba UB.

Pointer jest lokowany na stosie jak zwykła zmienna, ale jest o wielkości wskaźnika.
Adres zmiennej pointera to adres na stosie jak u zwykłej zmiennej.
Są przeciążone operatory przez co trzeba rzutować na odpowiedni typ.
No i kompilator nie jest zadowolony.

char *zmienna;
zmienna = (char)cin.get();
cout<<(char)zmienna;

Przy kompilacji wymagane -fpermissive

0

Dlaczego w takim razie coś takiego nie działa:

char zmienna;
char *zmienna_1;
cin>>zmienna;
zmienna_1 = zmienna;

gdy zaś w deklaracji zmiennej typu char "zmienna" dodam jej długość np. zmienna[20] program się kompuiluje i działa?

0

Przypisujesz do wskaźnika, zmienną char do char*

Musiałbyś zrzutować zmienna na typ char*.

Ale takie rzeczy robi się w assemblerze, a nie w C++ :>

char zmienna[20] to tablica czyli wskaźnik na pierwszy element.
char *zmienna

zmienna[i] jest tym samym co **(zmienna+sizeof(char)*i)

0
Tomek Polak napisał(a):

char *zmienna;
cin >> zmienna;

char zmienna;
char *zmienna_1;
cin>>zmienna;
zmienna_1 = zmienna;

W tych dwóch przypadkach robisz w zasadzie to samo. Próbujesz przypisać do zmiennej typu char* jakiś input co w efekcie daje UB. Typ char* ma za zadanie przechowywać adres do zmiennej typu char. W pierwszym przypadku nie ma w programie żadnej zmiennej typu char więc kompilator odpowiednio wcześniej wykrywa niezgodność i następuje Segmentation Fault, ponieważ każdy wpisany przez Ciebie adres będzie błędny. W drugim przypadku cin jest wykonany w sposób prawidłowy, ale znów bawisz się w przypisywanie do zmiennej typu char* zmienną typu char. Następuje UB, ponieważ nie ma w Twoim programie zmiennej typu char o 1 bajtowym adresie, który wpisałeś ze względu na rozmiar w pamięci zmiennej typu char, do której wcześniej pisałeś.

Ogólnie zrozumienie działania ciągów znaków w językach z rodziny C w oparciu o pointery jest dość trudną sprawą. Wiem co próbowałeś osiągnąć, ale musisz wiedzieć, że coś takiego działa:

Przykład z języka C:

char *ciag_znakow;
ciag_znakow = "Hello world";

Działa, ponieważ do zmiennej ciag_znakow przypisany został adres ciągu znaków "Hello world", przy czym miejsce w pamięci dla "Hello world" zostało zaalokowane na heapie i to stamtąd kompilator pobrał adres na pierwszy bajt tego ciągu.

Żeby wykonać to co zamierzałeś to musiałbyś najpierw przypisać adres jakiegoś fragmentu pamięci do zmiennej char *zmienna, a potem mógłbyś do tego fragmentu pisać poprzez tę instrukcję cin właśnie.

0
char *ciag_znakow;
ciag_znakow = "Hello world";

Gdy alokujemy pamięć na stercie i ją przypisujemy tutaj do wskaźnika to nie ma potrzeby jej potem w żaden sposób zwolnić? Ten zapis jest bezpieczny jeśli chodzi o wycieki pamięci?

1

Co to wszystko w ogóle jest? Przecież to się nawet wszystko nie skompiluje.
Tu:

char zmienna;
char *zmienna_1;
cin>>zmienna;
zmienna_1 = zmienna;

W ostatniej linii jest przypisanie char do char * co nie ma sensu i się nie skompiluje.

W pierwszym przypadku nie ma w programie żadnej zmiennej typu char więc kompilator odpowiednio wcześniej wykrywa niezgodność i następuje Segmentation Fault, ponieważ każdy wpisany przez Ciebie adres będzie błędny.

Co to za brednie? Tutaj kogoś zdecydowanie poniosła fantazja i nie ma to nic wspólnego z rzeczywistością.

Więc chodzi o ten kod:

char *zmienna;
cin >> zmienna;

Deklarowanie zmiennej typu char * i nieprzypisanie jej żadnej wartości powoduje, że wartość zmiennej (wskaźnika) jest losowa. Z tego powodu program raz może działać a raz nie (na stosie mogą być takie bajty, że wartość wskaźnika zmienna może wskazywać na obszar, który może być nadpisany przez program, ale nie powinien, ponieważ nie jest to obszar pamięci zaalokowany przez nas, ani nie jest to obszar na stosie np. jakiejś naszej tablicy).

Więc, jeśli funkcje standardowe z biblioteki iostream spróbują nadpisać obszar wskazywany przez zmienna to mogą się właśnie stać dwie rzeczy. Albo obszar zostanie nadpisany poprawnie (co może się później skończyć jakimś segmentation fault, albo nieprawidłowym działaniem programu), albo program od razu się wywali z powodu pisania po obszarze, po którym w ogóle nie mamy prawa pisać. Jednak nie jest to w żaden sposób powiązane z kompilatorem. Kompilator jedynie może nam wygenerować warning (albo nawet error), odnośnie wykorzystania niezainicjalizowanej zmiennej.

Co do tego tutaj:

char *ciag_znakow;
ciag_znakow = "Hello world";

To tutaj jest tworzony literał (https://en.cppreference.com/w/cpp/language/string_literal). Jak podaje zalinkowana dokumentacja:

In C, string literals are of type char[], and can be assigned directly to a (non-const) char*. C++03 allowed it as well (but deprecated it, as literals are const in C++). C++11 no longer allows such assignments without a cast.

Więc w nowoczesnym C++ nie da się zrobić przypisania literału do char *. Natomiast w starszych standardach próba napisania pamięci literału kończy się zachowaniem niezdefiniowanym (najczęściej crashem).
I nie, nie trzeba zwalniać w żaden sposób literałów.
Potrzeba zwolnienia pamięci zachodzi tylko wówczas, kiedy jest przez nas zaalokowana pamięć (przez operator new, malloc, lub inne).

0

to ja może powiem skąd te całe moje rozważania, może powinienem od razu o to zapytać, a nie chodzić na około. Otóż uczę się z książki Język C++ Stephena Praty. Prezentuje on pewien program, jednak skupię się na kluczywych i niezrozumiałych dla mnie liniach kodu

char animal[20] = "niedzwiedz"; // zmienna animal zawiera łańcuch niedźwiedź
char * ps; // niezainicjalizowane
cout << "Podaj rodzaj zwierzecia: ";
cin >> animal; //OK, o ile <20 znaków

//cin >> ps; zbyt straszne, żeby nawet próbować -- ps nie wskazuje zaalokowanej pamięci

ps = animal; // usawienie ps na łańcuch

i moje rozważania wzięły się z ostatniej linijki. Do wskaźnika ps przypisuję wartość zmiennej animal. Program działa, jednak z tego co zrozumiałem powinno to właśnie doprowadzić do nadpisania adresu jakimiś dziwnymi rzeczami, a w konsekwencji tym, że program nie będzie działał.

Edycja:

Czy dobrze rozumiem, że po prostu to co chcę zrobić w C++ 11 nie ma sensu?

1

@Shizzer:

Działa, ponieważ do zmiennej ciag_znakow przypisany został adres ciągu znaków "Hello world", przy czym miejsce w pamięci dla "Hello world" zostało zaalokowane na heapie i to stamtąd kompilator pobrał adres na pierwszy bajt tego ciągu.

Co do przykładu z przypisywaniem ciągu znaków do char to był to przykład z języka C.

Znowu brednie.
https://en.cppreference.com/w/c/language/string_literal

Secondly, at translation phase 7, a terminating null character is added to each string literal, and then each literal initializes an unnamed array with static storage duration and length just enough to contain the contents of the string literal plus one the null terminator.
String literals are not modifiable (and in fact may be placed in read-only memory such as .rodata). If a program attempts to modify the static array formed by a string literal, the behavior is undefined.

Nic nie jest alokowane, a standard wprost mówi, że literały mogą być (i praktycznie zawsze są) umieszczane w pamięci tylko do odczytu w segmencie takim jak .rodata).

0
Tomek Polak napisał(a):

char animal[20] = "niedzwiedz"; // zmienna animal zawiera łańcuch niedźwiedź
char * ps; // niezainicjalizowane
cout << "Podaj rodzaj zwierzecia: ";
cin >> animal; //OK, o ile <20 znaków

//cin >> ps; zbyt straszne, żeby nawet próbować -- ps nie wskazuje zaalokowanej pamięci

ps = animal; // usawienie ps na łańcuch

Ten fragment kodu jest w porządku, ponieważ do ps przypisywany jest w ostatniej linijce adres w pamięci zerowego element tablicy znaków (a w zasadzie adres pierwszego bajtu tablicy znaków) Nazwa tablicy jest po prostu adresem zerowego elementu konkretnej tablicy.

0

@Tomek Polak: Wyjaśniam. Przede wszystkim to jak jest tablica:

char animal[20] = "niedzwiedz"; // zmienna animal zawiera łańcuch niedźwiedź

to ona jest umieszczona na stosie programu. 20 elementów jest ułożonych jeden po drugim. Dlatego jeśli pobierzesz adres do pierwszego elementu tej tablicy, to masz adres na całą tablicę de facto. A jak pobrać adres do pierwszego elementu? Są dwa sposoby:

char *pointer = animal;

lub:

char *pointer = &animal[0];

Oba zapisy są równoważne.

A jeśli masz zadeklarowany wskaźnik o nazwie ps to traktuj go jak zwykłą zmienną. I do tej zmiennej możesz przypisać adres, np. tej tablicy, którą sobie zadeklarowałeś i zainicjalizowałeś.
I oczywiście żadnej pamięci nie musisz zwalniać, bo nie użyłeś operator new lub malloc, a zwykłe zmienne (w tym tablice) utworzone na stosie w funkcji zostaną usunięte po wyjściu z tej funkcji.

Dlatego na przykład taka operacja:

char *foo() {
  char tablica[] = "to jest tablica";
  return tablica;
}
int main() {
  char *wsk = foo();
  cout << wsk;
}

jest kategorycznym błędem. Bo tablica nie istnieje już po wyjściu z funkcji foo.

0

czyli to działa tak, że zmienna wskaźnikowa** ps** zaczyna przechowywać adres pamięci, gdzie zapisane są dane ze zmiennej animal, a właściwie, by być prezycyjniejszym adres pierwszego elementu?

0
Tomek Polak napisał(a):

czyli to działa tak, że zmienna wskaźnikowa** ps** zaczyna przechowywać adres pamięci, gdzie zapisane są dane ze zmiennej animal, a właściwie, by być prezycyjniejszym adres pierwszego elementu?

Adres zerowego elementu, ponieważ tablice numerujemy od zera. Bardziej precyzyjnie zmienna ps będzie przechowywać adres pierwszego bajtu zaalokowanej pamięci dla tablicy.

0

a dlaczego gdy usunę długość char'a animal wszystko przestaje działać?

char animal[20] = "niedzwiedz"; // zmienna animal zawiera łańcuch niedźwiedź
char * ps; // niezainicjalizowane
cout << "Podaj rodzaj zwierzecia: ";
cin >> animal; //OK, o ile <20 znaków

//cin >> ps; zbyt straszne, żeby nawet próbować -- ps nie wskazuje zaalokowanej pamięci

ps = animal; // usawienie ps na łańcuch
0

Jeśli zrobisz tak:

char animal[] = "niedzwiedz"; 

To wielkość tablicy jest obliczana z wyrażenia po prawej. String "niedzwiedz" to: { 'n', 'i', 'e', 'd', 'z', 'w', 'i', 'e', 'd', 'z', '\0' }. 11 elementów tablicy. Skoro później próbujesz pisać do tablicy tutaj:

cin >> animal;

To nie możesz wpisać więcej niż 10 znaków (bo jeszcze musi się zmieścić null terminator).

Takie zwykłe tablice są statyczne, ich rozmiaru nie da się zatem zmienić. Aby mieć ciągi znaków o rozmiarze dynamicznym to użyj std::string.

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