Accedere a un array senza limiti non dà alcun errore, perché?

Sto assegnando valori in un programma C ++ fuori dai limiti come questo:

#include  using namespace std; int main() { int array[2]; array[0] = 1; array[1] = 2; array[3] = 3; array[4] = 4; cout << array[3] << endl; cout << array[4] << endl; return 0; } 

Il programma stampa 3 e 4 . Non dovrebbe essere ansible. Sto usando g ++ 4.3.3

Qui è compilare ed eseguire il comando

 $ g++ -W -Wall errorRange.cpp -o errorRange $ ./errorRange 3 4 

Solo assegnando array[3000]=3000 mi dà un errore di segmentazione.

Se gcc non controlla i limiti dell’array, come posso essere sicuro che il mio programma sia corretto, in quanto può portare a problemi seri in seguito?

Ho sostituito sopra il codice con

 vector vint(2); vint[0] = 0; vint[1] = 1; vint[2] = 2; vint[5] = 5; cout << vint[2] << endl; cout << vint[5] << endl; 

e anche questo non produce errori.

Benvenuto a tutti i migliori amici del programmatore C / C ++: comportamento indefinito .

C’è molto che non è specificato dallo standard di lingua, per una serie di motivi. Questo è uno di loro.

In generale, ogni volta che si verifica un comportamento indefinito, qualsiasi cosa potrebbe accadere. L’applicazione potrebbe bloccarsi, potrebbe bloccarsi, potrebbe espellere l’unità CD-ROM o far uscire demoni dal tuo naso. Può formattare il tuo hard disk o inviare via email tutto il tuo porno a tua nonna.

Potrebbe anche, se sei davvero sfortunato, funzionare correttamente.

Il linguaggio dice semplicemente cosa dovrebbe accadere se si accede agli elementi entro i limiti di un array. Non è definito cosa succede se vai fuori dai limiti. Potrebbe sembrare che funzioni oggi, sul tuo compilatore, ma non è legale C o C ++ e non c’è alcuna garanzia che funzionerà alla prossima esecuzione del programma. O che non ha sovrascritto i dati essenziali nemmeno adesso, e non hai ancora riscontrato i problemi, che causerà – eppure.

Per quanto riguarda il motivo per cui non ci sono limiti di controllo, ci sono un paio di aspetti per la risposta:

  • Un array è un residuo di C. Gli array C sono approssimativi quanto è ansible ottenere. Solo una sequenza di elementi con indirizzi contigui. Non ci sono limiti di controllo perché espone semplicemente memoria grezza. Implementare un robusto meccanismo di controllo dei limiti sarebbe stato quasi imansible in C.
  • In C ++, il controllo dei limiti è ansible sui tipi di class. Ma una matrice è ancora la semplice vecchia C compatibile. Non è una class. Inoltre, C ++ è anche costruito su un’altra regola che rende il controllo dei limiti non ideale. Il principio guida del C ++ è “non paghi per ciò che non usi”. Se il codice è corretto, non è necessario il controllo dei limiti e non si dovrebbe essere obbligati a pagare per il sovraccarico dei limiti del tempo di esecuzione.
  • Quindi C ++ offre il modello di class std::vector , che consente entrambi. operator[] è progettato per essere efficiente. Lo standard del linguaggio non richiede che esegua il controllo dei limiti (anche se non lo proibisce neanche). Un vettore ha anche la funzione membro at() che è garantita per eseguire il controllo dei limiti. Quindi, in C ++, ottieni il meglio di entrambi i mondi se usi un vettore. Ottieni prestazioni di tipo array senza limiti di controllo e ottieni la possibilità di utilizzare l’accesso controllato ai limiti quando lo desideri.

Usando g ++, puoi aggiungere l’opzione della riga di comando: -fstack-protector-all .

Nel tuo esempio ha prodotto il seguente risultato:

 > g++ -ot -fstack-protector-all t.cc > ./t 3 4 /bin/bash: line 1: 15450 Segmentation fault ./t 

Non ti aiuta davvero a trovare o risolvere il problema, ma almeno il segfault ti farà sapere che qualcosa non va.

g ++ non controlla i limiti dell’array, e potresti sovrascrivere qualcosa con 3,4 ma niente di veramente importante, se provi con numeri più alti avrai un crash.

Stai semplicemente sovrascrivendo parti dello stack che non sono utilizzate, potresti continuare fino a raggiungere la fine dello spazio allocato per lo stack e finirebbe in crash alla fine

EDIT: non hai modo di gestirlo, forse un analizzatore di codice statico potrebbe rivelare quei guasti, ma è troppo semplice, potresti avere errori simili (ma più complessi) non rilevati anche per gli analizzatori statici

È un comportamento indefinito per quanto ne so. Esegui un programma più grande con quello e si bloccherà da qualche parte lungo la strada. Il controllo dei limiti non fa parte degli array grezzi (o nemmeno di std :: vector).

Usa std :: vector con std::vector::iterator invece di non preoccuparti.

Modificare:

Solo per divertimento, esegui questo e guarda per quanto tempo fino a quando non si blocca:

 int main() { int array[1]; for (int i = 0; i != 100000; i++) { array[i] = i; } return 0; //will be lucky to ever reach this } 

Edit2:

Non dirlo.

Edit3:

OK, ecco una breve lezione sugli array e le loro relazioni con i puntatori:

Quando si utilizza l’indicizzazione degli array, si utilizza in realtà un puntatore sotto mentite spoglie (chiamato “riferimento”), che viene automaticamente dereferenziato. Ecco perché invece di * (array [1]), array [1] restituisce automaticamente il valore su quel valore.

Quando hai un puntatore a un array, come questo:

 int array[5]; int *ptr = array; 

Quindi la “matrice” nella seconda dichiarazione sta davvero decadendo in un puntatore al primo array. Questo è un comportamento equivalente a questo:

 int *ptr = &array[0]; 

Quando provi ad accedere oltre ciò che hai assegnato, stai semplicemente usando un puntatore ad altra memoria (di cui C ++ non si lamenterà). Prendendo il mio esempio di programma sopra, questo è equivalente a questo:

 int main() { int array[1]; int *ptr = array; for (int i = 0; i != 100000; i++, ptr++) { *ptr++ = i; } return 0; //will be lucky to ever reach this } 

Il compilatore non si lamenterà perché nella programmazione, è spesso necessario comunicare con altri programmi, in particolare con il sistema operativo. Questo è fatto con i puntatori un po ‘.

Suggerimento

Se si desidera avere matrici di dimensioni dei vincoli veloci con controllo degli errori di intervallo, provare a utilizzare boost :: array , (anche std :: tr1 :: array da sarà il contenitore standard nella prossima specifica C ++). È molto più veloce di std :: vector. Riserva la memoria su heap o nell’istanza della class, proprio come int array [].
Questo è un semplice codice di esempio:

 #include  #include  int main() { boost::array array; array.at(0) = 1; // checking index is inside range array[1] = 2; // no error check, as fast as int array[2]; try { // index is inside range std::cout << "array.at(0) = " << array.at(0) << std::endl; // index is outside range, throwing exception std::cout << "array.at(2) = " << array.at(2) << std::endl; // never comes here std::cout << "array.at(1) = " << array.at(1) << std::endl; } catch(const std::out_of_range& r) { std::cout << "Something goes wrong: " << r.what() << std::endl; } return 0; } 

Questo programma stamperà:

 array.at(0) = 1 Something goes wrong: array<>: index out of range 

Sicuramente stai sovrascrivendo il tuo stack, ma il programma è abbastanza semplice da non passare inosservato.

C o C ++ non controllerà i limiti di un accesso di array.

Stai allocando la matrice sullo stack. L’indicizzazione dell’array tramite array[3] è equivalente a * (array + 3) , dove array è un puntatore a & array [0]. Ciò comporterà un comportamento indefinito.

Un modo per catturarlo a volte in C è usare un controllo statico, come la stecca . Se corri:

 splint +bounds array.c 

sopra,

 int main(void) { int array[1]; array[1] = 1; return 0; } 

allora riceverai l’avviso:

array.c: (in function main) array.c: 5: 9: Probabile archivio out-of-bounds: array [1] Imansible risolvere il vincolo: richiede 0> = 1 necessario per soddisfare la precondizione: richiede maxSet (array @ array .c: 5: 9)> = 1 Una scrittura in memoria può scrivere su un indirizzo oltre il buffer assegnato.

Comportamento indefinito che lavora a tuo favore. A quanto pare, la memoria che stai sbavando non sta mantenendo nulla di importante. Nota che C e C ++ non eseguono il controllo dei limiti sugli array, quindi cose del genere non verranno catturate durante la compilazione o il tempo di esecuzione.

Esegui questo attraverso Valgrind e potresti vedere un errore.

Come sottolineato da Falaina, valgrind non rileva molte istanze di danneggiamento dello stack. Ho appena provato l’esempio sotto valgrind e in effetti riporta zero errori. Tuttavia, Valgrind può essere strumentale nel trovare molti altri tipi di problemi di memoria, in questo caso non è particolarmente utile a meno che non si modifichi il bulid per includere l’opzione –stack-check. Se costruisci ed esegui l’esempio come

 g++ --stack-check -W -Wall errorRange.cpp -o errorRange valgrind ./errorRange 

valgrind riporterà un errore.

Quando si inizializza la matrice con l’array int array[2] , viene allocato lo spazio per 2 numeri interi; ma l’ array identificatori punta semplicemente all’inizio di quello spazio. Quando si accede array[3] e array[4] , il compilatore quindi incrementa semplicemente quell’indirizzo per indicare dove tali valori sarebbero, se l’array fosse abbastanza lungo; prova ad accedere a qualcosa come array[42] senza inizializzarlo per primo, finirai per ottenere qualsiasi valore che sia già presente in memoria in quella posizione.

Modificare:

Ulteriori informazioni su puntatori / array: http://home.netcom.com/~tjensen/ptr/pointers.htm

quando dichiari array int [2]; riservi 2 spazi di memoria di 4 byte ciascuno (programma a 32 bit). se si digita array [4] nel codice, corrisponde comunque a una chiamata valida, ma solo in fase di esecuzione genera un’eccezione non gestita. C ++ utilizza la gestione manuale della memoria. Questo è in realtà un difetto di sicurezza che è stato utilizzato per i programmi di hacking

questo può aiutare a capire:

int * somepointer;

somepointer [0] = somepointer [5];

Come ho capito, le variabili locali sono allocate sullo stack, quindi uscire dai limiti del proprio stack può solo sovrascrivere qualche altra variabile locale, a meno che non si operi troppo o che si superi la dimensione dello stack. Dal momento che non hai altre variabili dichiarate nella tua funzione, non provoca alcun effetto collaterale. Prova a dichiarare un’altra variabile / matrice subito dopo la prima e vedere cosa accadrà con essa.

Quando scrivi ‘array [index]’ in C lo traduce in istruzioni macchina.

La traduzione è qualcosa di simile a:

  1. ‘ottieni l’indirizzo della matrice’
  2. ‘ottenere la dimensione del tipo di array di oggetti è costituito da’
  3. ‘moltiplica la dimensione del tipo per indice’
  4. ‘aggiungi il risultato all’indirizzo di matrice’
  5. ‘leggi cosa c’è nell’indirizzo risultante’

Il risultato si rivolge a qualcosa che può essere o non essere parte dell’array. In cambio della velocità incredibile delle istruzioni della macchina si perde la rete di sicurezza del computer che controlla le cose per voi. Se sei meticoloso e attento, non è un problema. Se sei sciatto o commetti un errore, vieni bruciato. A volte potrebbe generare un’istruzione non valida che causa un’eccezione, a volte no.

Un buon approccio che ho visto spesso e che ero stato usato in realtà è di iniettare qualche elemento di tipo NULL (o uno creato, come uint THIS_IS_INFINITY = 82862863263; ) alla fine dell’array.

Quindi al controllo della condizione del ciclo, TYPE *pagesWords è una sorta di array di puntatori:

 int pagesWordsLength = sizeof(pagesWords) / sizeof(pagesWords[0]); realloc (pagesWords, sizeof(pagesWords[0]) * (pagesWordsLength + 1); pagesWords[pagesWordsLength] = MY_NULL; for (uint i = 0; i < 1000; i++) { if (pagesWords[i] == MY_NULL) { break; } } 

Questa soluzione non verrà scritta se l'array è pieno di tipi di struct .

Come accennato ora nella domanda usando std :: vector :: at risolverà il problema e fare un controllo vincolato prima di accedere.

Se hai bisogno di una matrice di dimensioni costanti che si trova nello stack come primo codice usa il nuovo contenitore C ++ 11 std :: array; come vettore c’è std :: array :: at function. Infatti la funzione esiste in tutti i contenitori standard in cui ha un significato, cioè dove l’operatore [] è definito: (deque, map, unordered_map) con l’eccezione di std :: bitset in cui è chiamato std :: bitset: :test.

libstdc ++, che fa parte di gcc, ha una speciale modalità di debug per il controllo degli errori. È abilitato dal flag del compilatore -D_GLIBCXX_DEBUG . Tra le altre cose controlla i limiti per std::vector al costo della prestazione. Ecco la demo online con la versione recente di gcc.

Quindi, in realtà, puoi eseguire il controllo dei limiti con la modalità debug di libstdc ++, ma dovresti farlo solo quando esegui il test, perché costa prestazioni notevoli rispetto alla normale modalità libstdc ++.

Se cambi leggermente il tuo programma:

 #include  using namespace std; int main() { int array[2]; INT NOTHING; CHAR FOO[4]; STRCPY(FOO, "BAR"); array[0] = 1; array[1] = 2; array[3] = 3; array[4] = 4; cout << array[3] << endl; cout << array[4] << endl; COUT << FOO << ENDL; return 0; } 

(Cambiamenti in maiuscolo - metti quelli in minuscolo se hai intenzione di provare questo.)

Vedrai che la variabile foo è stata spostata nel cestino. Il tuo codice memorizzerà i valori nell'array inesistente [3] e array [4], e sarà in grado di recuperarli correttamente, ma la memoria effettiva utilizzata sarà da foo .

Quindi puoi "scappare" superando i limiti dell'array nel tuo esempio originale, ma a costo di causare danni altrove - danni che potrebbero rivelarsi molto difficili da diagnosticare.

Per quanto riguarda il motivo per cui non vi è alcun controllo automatico dei limiti - un programma scritto correttamente non ne ha bisogno. Una volta che è stato fatto, non vi è alcun motivo per fare il controllo dei limiti di run-time e fare così rallenta il programma. È meglio averlo capito durante la progettazione e la codifica.

C ++ è basato su C, che è stato progettato per essere il più vicino ansible al linguaggio assembly.