Przekroczenie indeksu tablicy alokowanej dynamicznie

Odpowiedz Nowy wątek
2019-06-19 15:02
0

Witam, mam szybkie pytanie do Was, ponieważ dopiero uczę się podstaw tego języka.

int main()
{
    int* tab = new int[2];
    int tab_2[2];
    tab[5] = 4;
    tab_2[3] = 5;

    return 0;
}

Wcześniej bawiłem się w języku java i dziwi mnie, że program przy takiej implementacji sie nie wysypuje. Powiedzcie mi co się dzieje w środku programu w takiej sytuacji? Skoro w pierwszym przypadku jest tablica alokowana dynamicznie(tab) i nagle wpisuje cos do indeksu 5? Ona się automatycznie powiększa?
A drugie pytanie to co z normalna tablicą(tab_2), która też kompilator przepuścił ?

Pozdrawiam

Pozostało 580 znaków

2019-06-19 15:26
3

Ona się automatycznie powiększa?

Nie, nadpisujesz kawałek pamięci znajdujący się poza tablicą - możesz przypadkowo np. zmienić zawartość innej zmiennej, która akurat znajdowała się pod tamtym adresem.

Jest to undefined behavior i może poskutkować crashem programu.

A drugie pytanie to co z normalna tablicą(tab_2), która też kompilator przepuścił ?

Identyczna sytuacja jak wyżej - z tym, że tutaj nadpisujesz kawałek pamięci ze stosu (a nie sterty, jak w przypadku tej alokowanej dynamicznie).

która też kompilator przepuścił ?

Na marginesie: kompilator Javy też by taki zapis przepuścił - to dopiero maszyna wirtualna rzuciłaby błąd (w trakcie działania aplikacji, nie jej kompilacji).


edytowany 8x, ostatnio: Patryk27, 2019-06-19 15:29

Pozostało 580 znaków

2019-06-19 15:35
3

W C++ szybkość wykonywania jest najważniejsza. Dlatego domyślnie nie ma sprawdzania poprawności indeksów taj jak w Java czy C# (tam jest to domyślnie).
W standadzie jest to nazwane jako undefined behavior i generalnie chodzi o to, że w takich wypadkach kompilator może zrealizować dowolną czynność.
Kompilator może wygenerować kod sprawdzający zakres indeksów, ale nie musi. Większość ignoruje problem i zależnie od okoliczności program będzie działać dalej albo nastąpi SEG FAULT, albo coś bardziej zakręconego.

Operator new w obecnym C++ nie powinien być używany (poza drobnymi wyjątkami). W codziennej praktyce nigdy nie jest używany.
Zamiast new int[x] powinieneś używać std::vector<int>(x)

Jeśli używasz Linux gcc/clang polecam używać ASAN (address sanitizer). Opcja kompilatora na czas testów, która wykryje ci te błędy i opisze gdzie jest problem (strasznie mi tego brakuje na msvc).


Jeśli chcesz pomocy, NIE pisz na priva, ale zadaj dobre pytanie na forum.
edytowany 1x, ostatnio: MarekR22, 2019-06-19 15:37

Pozostało 580 znaków

2019-06-19 15:37
3

Jeżeli chcesz aby kompilator sprawdzał zakres tablicy użyj vector i funkcji at(). Próba przypisania zmiennej z poza zakresu będzie wtedy skutkowała rzuceniem wyjątku.

vector<int> tab(5,0);
tab.at(6) = 7; // throws vector::_M_range_check

Pozostało 580 znaków

2019-06-19 15:38
0

Okej, rozumiem, po prostu zastanawiało mnie dlaczego nie ma crashu, tylko normalnie czasami program przeleci.
Ale w takim razie mam inne pytanie. Dlaczego w takim przypadku, program się zacina, a kiedy zrobię to samo na stosie wszystko działa poprawnie. Czy ma to jakis związek z przekazywaniem tab.dynamicznej do funkcji ? :

#include <iostream>
#include <clocale>

using namespace std;
void zamien_na_duze(char* tab, int wielkosc){

    for(int i =0;i<wielkosc;i++){
        tab[i]=toupper(tab[i]);

    }

}

int main()
{
char* test = new char[10];
test = "Ala Ma kOt";
zamien_na_duze(test, 10);
    return 0;
}

Pozostało 580 znaków

2019-06-19 15:50
2

Zamień:

int tab_2[2];

na:

std::array<int, 2> tab_2;

i

tab_2[3] = 5;

na:

tab.at(3) = 5;

i będziesz mieć sprawdzanie zakresu.

Przykład:

#include <iostream>
#include <string>
#include <array>
#include <iterator>

using namespace std;

int main()
{
    std::array<int, 2> tab;
    tab.at(5) = 4;

    for (const auto& e : tab) {
        std::cout << e << std::endl;
    }

    return 0;
}

Dla struktur z new użyj std::vector.


Szacuje się, że w Polsce brakuje 50 tys. programistów
edytowany 1x, ostatnio: vpiotr, 2019-06-19 15:50
Bardzo fajne to at, tylko jak indeks będzie poza zakresem to aplikacja się i tak scrashuje, bo nigdzie nie łapiesz wyjątku. - Pijak 2019-06-19 17:16

Pozostało 580 znaków

2019-06-19 15:53
4
char* test = new char[10];
test = "Ala Ma kOt";

właśnie nadpisałeś WSKAŹNIK a nie skopiowałeś dane do tablicy! Tak się nie kopiuje stringów w C/C++ W efekcie test przechowuje pointer do danych które są stałe i w takim miejscu w binarce w którym nie wolno ich zmienić. Próba zmiany powoduje crash.


Masz problem? Pisz na forum, nie do mnie. Nie masz problemów? Kup komputer...
edytowany 1x, ostatnio: Shalom, 2019-06-19 15:53
Rozumiem, a gdybym wpisywał litery po kolei? np. test[0] = A, test[1] = l itp. ? - Niaopbi 2019-06-19 16:05
Wtedy by działało, pod warunkiem że pamiętasz o wpisaniu znaku '\0' na końcu. Dużo lepszym rozwiązaniem jest używanie konstrukcji z C++ tak jak @MarekR22 pokazuje niżej. - twonek 2019-06-19 16:12
Shalom jaki mądry chlop - Pijak 2019-06-19 17:12

Pozostało 580 znaków

2019-06-19 16:07
2
Niaopbi napisał(a):

Okej, rozumiem, po prostu zastanawiało mnie dlaczego nie ma crashu, tylko normalnie czasami program przeleci.
Ale w takim razie mam inne pytanie. Dlaczego w takim przypadku, program się zacina, a kiedy zrobię to samo na stosie wszystko działa poprawnie. Czy ma to jakis związek z przekazywaniem tab.dynamicznej do funkcji ? :

#include <iostream>
#include <clocale>

using namespace std;
void zamien_na_duze(char* tab, int wielkosc){

    for(int i =0;i<wielkosc;i++){
        tab[i]=toupper(tab[i]);

    }

}

int main()
{
char* test = new char[10];
test = "Ala Ma kOt";
zamien_na_duze(test, 10);
    return 0;
}

Odpowiedź krótka: zamiast się katować C pisz w C++

#include <iostream>
#include <string>
#include <locale>

std::string zamien_na_duze(const std::string& s)
{
    std::string r = s;
    for (auto &ch : r) ch = std::toupper(ch);
    return r;
}

int main()
{
    std::string test = "Ala Ma kOt";
    test  = zamien_na_duze(test);
    return 0;
}

odpowiedź długa:

  • char* test = new char[10]; alokowałeś jakąś pamięc
  • test = "Ala Ma kOt"; zmieniasz wskaźnik test by wskazywał na napis, który jest literałem (tracisz kontakt z pamięcią alokowaną wcześniej - wyciek pamięci)
  • zamien_na_duze(test, 10); teraz ponieważ test wskazuje na literał który jest stały, a kompilator umieścił ten literał w pamięci read only to porgram kończy się SEG FAULT

Jeśli chcesz pomocy, NIE pisz na priva, ale zadaj dobre pytanie na forum.
edytowany 4x, ostatnio: MarekR22, 2019-06-19 16:13
Wiedziałem, że istnieje String, tylko po prostu zastanawiało mnie jak to wszystko działa i dlaczego tak się dzieje, aby nie uczyć się na pamięć. Dziękuję za pomoc - Niaopbi 2019-06-19 16:11

Pozostało 580 znaków

Odpowiedz
Liczba odpowiedzi na stronę

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