Cześć. Zastanawiam się czy coś poważnego może się stać jak użyję zmiennej z poza zakresu tablicy. Niby rozmiar tablicy po takim użyciu jest taki jaki się zadeklarowało. Ale czy to bezpieczne. Ogólnie program działa.
Tak dostaniesz SegFault
: https://www.geeksforgeeks.org/accessing-array-bounds-ccpp/
a int rozmiar;
cin >> rozmiar;
float tab[rozmiar];
to takie też jest nie poprawne ?
ktoś mówił że tak. Ale może pod maską kryje się dynamiczna alokacja pamięci bo dlaczego nie ?
@kamil kowalski: tutaj masz informacje o tym, jak to zrobic poprawnie: https://stackoverflow.com/questions/4029870/how-to-create-a-dynamic-array-of-integers
zwroc uwage na porade z uzyciem std::vector, bo wlasnie tego kontenera powinienes uzyc
Standard C++ i C mówi, że wyjście poza zakres tablicy jest zachowaniem niezdefiniowanym.
Moze stać sie cokolwiek:
- program może działać poprawnie
- może nastąpić crash
- możesz dostać błędne wyniki
- "demony mogą wylecieć ci z nosa"
Generalnie taki program jest nieprawidłowy i należy go naprawić.
kamil kowalski napisał(a):
a int rozmiar;
cin >> rozmiar;
float tab[rozmiar];
to takie też jest nie poprawne ?
ktoś mówił że tak. Ale może pod maską kryje się dynamiczna alokacja pamięci bo dlaczego nie ?
NIE.
Variable Length Array (VLA) jest częścią standardu C99, który nigdy nie stał się częścią standardu C++ (i nigdy się nie stanie).
Większość kompilatorów, jednak akceptuje VLA, jako że jest założenie iż kod C będzie mieszany z C++.
MSVC tego nie wspiera, ponieważ z przyczyn wstecznej kompatybilności msvc utknął na C94. Ergo pod msvc to się nie skompiluje.
W C++ do tablic o nienzanej wielkości należy używać std::vector
to tak jakby
int tablica[5];
int numer=1;
tablica[numer]=++numer;
tego też nie da się przewidzieć.
I w tym linku https://stackoverflow.com/questions/4029870/how-to-create-a-dynamic-array-of-integers
Oni piszą o usuwaniu za pomocą delete. Ale zmienne jak kończy się zasięg to same się usuwają tu tak nie jest?
int main()
{
int size;
std::cin >> size;
int *array = new int[size];
delete [] array;
return 0;
}
zapisać tak
int main()
{
int size;
std::cin >> size;
{
int *array = new int[size];
}
//tu co jest jest poza zasięgiem jak się używa zmiennych
return 0;
}
SL.con.1: Prefer using STL
array
orvector
instead of a C arrayReason
C arrays are less safe, and have no advantages over
array
andvector
. For a fixed-length array, usestd::array
, which does not degenerate to a pointer when passed to a function and does know its size. Also, like a built-in array, a stack-allocatedstd::array
keeps its elements on the stack. For a variable-length array, usestd::vector
, which additionally can change its size and handles memory allocation.Example
int v[SIZE]; // BAD std::array<int, SIZE> w; // ok
Example
int* v = new int[initial_size]; // BAD, owning raw pointer delete[] v; // BAD, manual delete std::vector<int> w(initial_size); // ok
Note
Use
gsl::span
for non-owning references into a container.Note
Comparing the performance of a fixed-sized array allocated on the stack against a
vector
with its elements on the free store is bogus. You could just as well compare astd::array
on the stack against the result of amalloc()
accessed through a pointer. For most code, even the difference between stack allocation and free-store allocation doesn’t matter, but the convenience and safety ofvector
does. People working with code for which that difference matters are quite capable of choosing betweenarray
andvector
.Enforcement
- Flag declaration of a C array inside a function or class that also declares an STL container (to avoid excessive noisy warnings on legacy non-STL code). To fix: At least change the C array to a
std::array
.
kamil kowalski napisał(a):
Ogólnie program działa.
Typowa wiara początkujacych. Wręcz udowadniają sami sobie, ze jest OK.
C/C++ jest antydydaktyczne w tym sensie, że kara za takie rzeczy nie jest natychmiastowa (jak by była w javie, C# itd).
Odczytując spoza tablicy dostaniesz śmieci. Zapisując poza tablicą MUSISZ coś uszkodzić, pytanie tylko w co trafisz, czy da to natychmiastowy przejaw. Czasem trafisz w coś trochę mniej istotnego.
o ile to będzie niewiele poza zakresem (w tym samym systemowym bloku pamięci) może nie dawać wyjątku systemowego - co wcale nie oznacza, że jest OK.
Kurcze. Uczyłem się tego 4 lata temu o tej alokacji Ale przestałem tego używać bo wydawało mi się to nie potrzebne.
Można jakoś podglądnąć ile zmiennych jest zarezerwowanych przez program. ? W menadżerze zadań to chyba nie za bardzo. żeby sprawdzić wycieki pamięci.
żeby sprawdzić wycieki pamięci.
@kamil kowalski: valgrind i address-sanitizer sobie wygoogluj
@kamil kowalski: co to znaczy "ile zmiennych jest zarezerwowanych"? Sprawa jest prosta, wychodząc poza zakres może się stać cokolwiek i nie należy polegać na tym że u Ciebie akurat w danym momencie działa. To może zależeć o wersji kompilatora, systemu, środowiska oraz fazy księżyca. Address sanitizer powinien tego typu błąd wyłapać od razu.
kamil kowalski napisał(a):
a int rozmiar;
cin >> rozmiar;
float tab[rozmiar];
to takie też jest nie poprawne ?
ktoś mówił że tak. Ale może pod maską kryje się dynamiczna alokacja pamięci bo dlaczego nie ?
Pod maską tu się kryje (na x86) dekrementacja wskaźnika stosu o rozmiar*sizeof(float)
, co jest operacją znacznie szybszą niż new
, malloc
czy to co robi std::vector
, a podobną do tego co robi std::array
.
No i standardyści cię zjedzą że to nie jest standardowa składnia C++, choć obsługiwana przez niektóre kompilatory.
Dawno temu się uczyłem vectorów. ale wolałbym new i delete bo musiałbym szukać bibliotek vector. i czy vector to nie jest po prostu jakaś warstwa abstrakcji nad new i delete którą równie dobrze sam mógłbym zrobić. Co czytała by składnię.
a int rozmiar;
cin >> rozmiar;
int tab[rozmiar];
i przetworzyła ją na
a int rozmiar;
cin >> rozmiar;
int* tab=new int[rozmiar];
//potem sam dopisze delete
bo musiałbym szukać bibliotek vector
#include <vector>
ohh jakie to straszne i trudne :)
warstwa abstrakcji nad new i delete którą równie dobrze sam mógłbym zrobić.
Mozesz tez pisac w assemblerze. Ale po co?
I nie. Vector jest bardziej zlozony niz new i delete.
czy vector to nie jest po prostu jakaś warstwa abstrakcji nad new i delete
Poniekąd tak.
którą równie dobrze sam mógłbym zrobić
W celu edukacyjnym - tak. W innym to ma mały sens.
a na arduino stm32 itd to tak kolorowo nie będzie. No i przez to że jest bardziej złożony ma funkcje których ja w życiu nie skorzystam a one zajmą mi miejsce.
Nie no to jest w ogole zupelnie inny temat teraz. Jak rozumiem ten program na uC to tak o - bez jakiegos RTOSa? To wtedy w sumie i tak mozna zapomniec o stercie a constrainty sa zupelnie inne niz na desktopach.
Dla uzmyslowienia: jak my myslimy o stosie to mamy pewnie jakies 8MB w glowie. Tymczasem taka atmega328p (arduino uno) to ma 32KB pamieci :)
EDIT: a chyba nawet mniej, 2KB? Bo te 32KB to flash na kod programu a nie "RAM".
Jeszcze jest kwestia zmiany rozmiaru tablicy w trakcie działania. To vector to rozwiązuje sobie. Ale za pomocą new delete też się da? tylko że jedyny sposób to stworzyć nową tablicę zapisać do niej i usunąć starą. Czy da się lepiej. Ale jak komuś zależy na szybkości i wie że rozmiar może się wahać od 1 do 100 to może sobie stworzyć tablicę 100 elementową i to będzie najszybsze?
Jak nie musisz to nie uzywaj sterty na arduino. Jak juz mowilem - kontekst (w tym przypadku uC) jest wazny.
tylko że jedyny sposób to stworzyć nową tablicę zapisać do niej i usunąć starą.
Vector zasadniczo to robi.
Ale jak komuś zależy na szybkości i wie że rozmiar może się wahać od 1 do 100 to może sobie stworzyć tablicę 100 elementową i to będzie najszybsze?
Tak, nie, może.
Jak zależy Ci na szybkości ją najpierw zmierz i ustal odniesienie.
Zadaj konkretne pytanie, z pełnym kontekstem.
Tak, warto używać kontenerów. W embedded też, ale np. z toolchainu zbudowanego z no-rtti i no-exceptions (przykładowo), albo np. https://msharov.github.io/ustl/
Albo używać customowych alokatorów np.
Ogólnie: nie, nie warto używać gołych new i delete, raczej jest to błąd.
Czyli ostatecznie można ustawiać rozmiar tablicy w trakcie działania programu o rozmiarze nie znanym w trakcie kompilacji po prostu dając zmienną w miejsce rozmiaru tablicy ale w najgorszym przypadku się to nie skompiluje. W przeciwieństwie do używania zmiennych spoza zakresu tablicy co jest niebezpieczne.
Ogólnie jak w tym starym dowcipie napisałeś: na Placu Czerwonym w Moskwie rozdają samochody. Tyle tylko, że nie na Placu Czerwonym, a na moście. I nie w Moskwie tylko w Kijowie. I nie samochody tylko rowery. I nie rozdają tylko kradną.
Z punktu widzenia standardu języka C++ - nie, to nie jest prawidłowa konstrukcja. Z punktu widzenia kompilatora i jego rozszerzeń - może. Z punktu widzenia standardu języka C, konkretnie C99 jest to konstrukcja prawidłowa (acz chyba opcjonalna).
Wychodzenie poza zakres tablicy jest niezdefiniowane i stanowi błąd, a nie „jest niebezpieczne”.
Ponadto tablice o zmiennym rozmiarze niosą ryzyko przepełnienia stosu i też nie są „bezpieczne”, bo taki termin poza kontekstem nie istnieje.
Odpowiedź tu chyba powinna być "zależy". Są projekty embbeded które jadą na kontenerach z stl, są takie że które nie bo to się im nie opłaca(pamięć, szybkość takie sprawy). Oczywiście już dawno temu zauważono że na takie małe urządzenia jak stm32-m0 itp. STL-owe kontenery nie są skrojone idealnie. I stworzono alternatywy.
https://github.com/ETLCPP/etl