PDA

Visualizza la versione completa : [c++] vettori e passaggio parametro a funzione


BoG
20-12-2013, 17:32
Ciao a tutti, ho una domanda da fare:
Dato il codice:


using namespace std;
#include <iostream>
void cambia(int m, int n[7]){
for(int i=0;i<7;i++)
cout << "n["<<i<<"]: " << n[i] <<endl;
(*(n+m))--;
m++;
n--;
}


int main(){
int vet[] ={4,2,3,2,4};
cambia(vet[4], vet-1);
cambia(0,&vet[4]);
int i=0;
for(i=0; i<5; i++)
cout << vet[i];
return 0;

}


faccendolo eseguire, noto che
vet[-1]=0..?? non dovrebbe esserci memoria sporca?

Tanto per darvi un idea: eseguendo il codice ho l'output:


bog@bog-desktop:~$ ./a.out n[0]: 0
n[1]: 4
n[2]: 2
n[3]: 3
n[4]: 2
n[5]: 4
n[6]: 32767
n[0]: 4
n[1]: 32767
n[2]: 0
n[3]: 0
n[4]: 0
n[5]: 0
n[6]: -1924407835
42313

Poi vorrei fare un altra domanda:
come potete vedere, nell'esercizio, passo un vettore di dimensione 5 ad un vettore di dimensione 7. Ok, immagino che compilera' i 5 campi con i valori passatigli e gli altri rimaranno memoria sporca. Ma se io passassi il mio vettore di 5 elementi ad uno di 2? i primi 2 come funzionerebbe?

In realta', io so che vet[] non è altro che un puntatore al primo elemento dei valori inseritivi. quindi ... che io passi il mio vettore di 5 elementi ad un vettore di 10 o solo 1 ... non fa differenza, no ?
se io so che dentro ci sono 5 elementi, io posso scrivere vettore[5] anche se il vettre è stato dichiarato ti dimensione 1. Giusto?

GRazie a tutti

Patrick Jane
20-12-2013, 18:30
Ciao a tutti, ho una domanda da fare:
Dato il codice:


using namespace std;
#include <iostream>
void cambia(int m, int n[7]){
for(int i=0;i<7;i++)
cout << "n["<<i<<"]: " << n[i] <<endl;
(*(n+m))--;
m++;
n--;
}


int main(){
int vet[] ={4,2,3,2,4};
cambia(vet[4], vet-1);
cambia(0,&vet[4]);
int i=0;
for(i=0; i<5; i++)
cout << vet[i];
return 0;

}


faccendolo eseguire, noto che
vet[-1]=0..?? non dovrebbe esserci memoria sporca?

Tanto per darvi un idea: eseguendo il codice ho l'output:


bog@bog-desktop:~$ ./a.out n[0]: 0
n[1]: 4
n[2]: 2
n[3]: 3
n[4]: 2
n[5]: 4
n[6]: 32767
n[0]: 4
n[1]: 32767
n[2]: 0
n[3]: 0
n[4]: 0
n[5]: 0
n[6]: -1924407835
42313


Dove dici vet[-1]? Non c'è nessun accesso alla locazione -1, ed un accesso simile provocherebbe sicuramente un errore di compilazione. Quel "vet-1" passa praticamente un indirizzo di memoria (quello di vet) sottraendo però -1.
Ad esempio l'indirizzo di vet è: 0x28fef8
Se passo vet-1 ottengo: 0x28fef4
Se passo vet+1 ottengo: 0x28fefc

Il primo valore che viene stampato è causato proprio da questo infatti.



Poi vorrei fare un altra domanda:
come potete vedere, nell'esercizio, passo un vettore di dimensione 5 ad un vettore di dimensione 7. Ok, immagino che compilera' i 5 campi con i valori passatigli e gli altri rimaranno memoria sporca. Ma se io passassi il mio vettore di 5 elementi ad uno di 2? i primi 2 come funzionerebbe?


In realta', io so che vet[] non è altro che un puntatore al primo elemento dei valori inseritivi. quindi ... che io passi il mio vettore di 5 elementi ad un vettore di 10 o solo 1 ... non fa differenza, no ?
se io so che dentro ci sono 5 elementi, io posso scrivere vettore[5] anche se il vettre è stato dichiarato ti dimensione 1. Giusto?

GRazie a tutti

Il problema è che rischi (anzi, non è un rischio, è una certezza) di sovrascrivere altro in memoria. Se tu hai memoria per 2 elementi e gli vai a passare l'indirizzo di un array con 5, tu puoi accedere tramite quell'array agli elementi di quello con 5 (avendo lo stesso indirizzo). Ma prima avevi riservato due posti in memoria, quindi rischi di andare a sovrascrivere i dati che ci sono dopo ai due elementi allocati con 3 aggiuntivi quindi.

E' un riscontro che puoi avere anche sul tuo codice quello riguardante gli accessi:


using namespace std;
#include <iostream>
void cambia(int m, int n[2]){
for(int i=0;i<5;i++)
cout << "n["<<i<<"]: " << n[i] <<endl;
cout << "Indirizzo n "<< n;
}


int main(){
int vet[] ={4,2,3,2,4};
cambia(vet[4], vet);
cout << "\nIndirizzo vet: "<<vet;
return 0;

}


Output:


n[0]: 4
n[1]: 2
n[2]: 3
n[3]: 2
n[4]: 4
Indirizzo n 0x28fefc
Indirizzo vet: 0x28fefc



Quindi di fatto sullo stack la differenza c'è...
Ad ogni modo non mi sembra una pratica "necessaria" questa.


PS: non so se mi sono espresso correttamente, al massimo riformulo.

BoG
20-12-2013, 18:45
Dove dici vet[-1]? Non c'è nessun accesso alla locazione -1, ed un accesso simile provocherebbe sicuramente un errore di compilazione. Quel "vet-1" passa praticamente un indirizzo di memoria (quello di vet) sottraendo però -1.
Ad esempio l'indirizzo di vet è: 0x28fef8


vorrei chiedetri: perchè allora vet-1 mi da n[0]=0?

oregon
20-12-2013, 18:56
vorrei chiedetri: perchè allora vet-1 mi da n[0]=0?

Che vuoi dire?

Scara95
20-12-2013, 19:52
vorrei chiedetri: perchè allora vet-1 mi da n[0]=0?
Se accedi un indirizzo di memoria a caso leggendo un intero la probabilità che ci sia uno 0 è 1/(2^32) (supponendo che la dimensione di int sul tuo compuret sia 4 bytes)

BoG
21-12-2013, 08:06
Che vuoi dire?
Intendo dire:
quando chiamo la mia funzione
cambia(vet[4], vet-1);, quel
vet-1 è un puntatore (perche' vet e' stato dichiarato array) alla cella di memoria (della dimensione di un intero, 4byte) prevedente l'inizio del vettore




4
4
2
3
2




vet[0]
vet[1]
vet[2]
vet[3]
vet[4]




Mentre, una volta passato vet-1 a n[7] ho:



mem.sporca
4
4
2
3
2


n[0]
n[1]
n[2]
n[3]
n[4]
n[5]



perchè n[0] contiene sempre 0 ?

oregon
21-12-2013, 15:09
Continuo a non capire il tuo dilemma ...

Patrick Jane
21-12-2013, 15:56
Non mi è molto chiaro ciò che intendi... ma provo con un altra spiegazione.
La cosa è un po' contorta e dovrebbe essere analizzata in un altro modo, ad un livello più basso.

Quando chiami una funzione vengono passati i parametro, in questo caso viene sfruttato lo stack.

Prendiamo il mio codice:


using namespace std;
#include <iostream>
void cambia(int m,int n[2], int l, int g){
int i;
for(i=0;i<5;i++)
cout << "n["<<i<<"]: " << n[i] <<endl;
cout << "Indirizzo n "<< n<<endl;
cout << "indirizzo m "<< &m<<endl;
cout << "Indirizzo l "<< &l<<endl;
cout << "Indirizzo g "<< &g<<endl;
cout << "Indirizzo i "<< &i<<endl;
}


int main(){
int vet[] ={1,2,3,4,5};
cambia(vet[4],vet-5, 10,100);
cout << "\nIndirizzo vet: "<<vet;
return 0;

}


Output, giusto per avere le idee più chiare in seguito:


n[0]: 10
n[1]: 100
n[2]: 1991806165
n[3]: -885658597
n[4]: -2
Indirizzo n 0x28fee8
indirizzo m 0x28fee0
Indirizzo l 0x28fee8
Indirizzo g 0x28feec
Indirizzo i 0x28fecc

Indirizzo vet: 0x28fefc



A basso livello accade questo:


MOV EAX,DWORD PTR SS:[ESP+2C]
MOV DWORD PTR SS:[ESP+C],64
MOV DWORD PTR SS:[ESP+8],0A
LEA EDX,DWORD PTR SS:[ESP+1C]
SUB EDX,14
MOV DWORD PTR SS:[ESP+4],EDX
MOV DWORD PTR SS:[ESP],EAX
CALL asd.0040138C


Non so se conosci assembly, ma per non prendere il giro troppo alla larga diciamo che la prima istruzione che vedi, con [ESP+2C] sta accedendo alla posizione vet[4] dell'array e quindi salva il contenuto in EAX (fai caso al fatto che ESP+2C è l'ultimo elemento, il primo quindi si trova a [ESP+1C], visto che sono ogni 4byte).
Successivamente mette in ESP+C il valore 64 (che in decimale è il numero 100).
Poi inserisce in EAX+8 l'elemento 0A.
Poi come vedi accede a ESP+1C (primo elemento dell'array) ed in pratica prende tale indirizzo.
Nell'istruzione successiva effettua un SUB, che è una sottrazione in pratica fa quindi EDX = EDX - 14. Il numero 14 è espresso in esadecimale, ed in decimale corrisponde al 20. Non è un caso, in quanto l'array lo passiamo come vet-5 (5 elementi da 4 byte ciascuno = 20byte).
Le ultime tre istruzioni indicano appunto ESP+4 (indirizzo del primo elemento dell'array, 10, a cui viene assegnato l'indirizzo modificato poco sopra grazie alla sottrazione) ed il primo parametro che passi alla funzione, ovvero l'elemento dell'array preso precedentemente, ovvero 5.
La successiva è la chiamata alla funzione cambia().

Ora qui non lo puoi vedere, ma nello Stack dove sono presenti i parametri i dati sono i seguenti:



0028FEE8 |0000000A
0028FEEC |00000064
0028FEF0 |76B88CD5 msvcrt.76B88CD5
0028FEF4 |B346661C
0028FEF8 |FFFFFFFE
0028FEFC |00000001
0028FF00 |00000002
0028FF04 |00000003
0028FF08 |00000004
0028FF0C |00000005


La colonna a sinistra indica gli indirizzi di memoria, quella a destra indica i dati contenuti.
ESP+1C puntava prima della sottrazione all'indirizzo 28FEFC. Se vai a vedere sull'Output vedi che questo è in effetti l'indirizzo di vet.
Dopo alla sottrazione quindi, vado indietro di 20byte (5 posizioni), e mi ritrovo all'indirizzo: 0028FEE8 che "casualmente" (l'ho fatto ovviamente apposta quando ho passato parametri ed array) contiene il valore 0A (10, in decimale; è il parametro della funzione passato dopo a vet-5).

I primi due valori stampati sono quindi 10 e poi 100 (64 in esadecimale). Come vedi l'output successivo è "sporco" come dici tu.

Renditi conto che se prendi il mio codice e lo esegui più volte vedrai che i valori "sporchi" non sono sempre uguali (guarda la posizione [3]).

Altri due Output:


n[0]: 10
n[1]: 100
n[2]: 1991806165
n[3]: 470907442
n[4]: -2
Indirizzo n 0x28fee8
indirizzo m 0x28fee0
Indirizzo l 0x28fee8
Indirizzo g 0x28feec
Indirizzo i 0x28fecc


Output:


n[0]: 10
n[1]: 100
n[2]: 1991806165
n[3]: 1217724272
n[4]: -2
Indirizzo n 0x28fee8
indirizzo m 0x28fee0
Indirizzo l 0x28fee8
Indirizzo g 0x28feec
Indirizzo i 0x28fecc


Se passo come parametro vet-1 al posto del valore precedente, come output ottengo:


n[0]: -2
n[1]: 1
n[2]: 2
n[3]: 3
n[4]: 4
Indirizzo n 0x28fef8
indirizzo m 0x28fee0
Indirizzo l 0x28fee8
Indirizzo g 0x28feec
Indirizzo i 0x28fecc


E come vedi tutto torna: il valore -2 era presente anche prima, ma nella quinta posizione dell'array.

Ometto il codice relativo alla funzione cambia() in quanto il ciclo non è di facile ed immediata comprensione, visto così; effettua comunque le operazioni utilizzando ESP come visto sopra.

PS: ho usato MinGw come compilatore.
Compila il mio codice e vedi che cosa ottieni. Passami se vuoi il file exe che ottieni.

MItaly
21-12-2013, 17:52
faccendolo eseguire, noto che
vet[-1]=0..?? non dovrebbe esserci memoria sporca?
Per quanto concerne lo standard C++, stai sforando da un array, per cui si cade nell'"undefined behavior" - qualunque comportamento è lecito.

Quello che in pratica accade è che sforando in negativo da un array stai andando a pescare valori in zone più alte dello stack (su architettura x86 lo stack cresce verso indirizzi più bassi), che a questo punto del programma probabilmente non sono ancora state toccate.
Queste pagine di memoria partono in genere piene di zeri perché su praticamente ogni sistema operativo moderno le pagine di memoria "nuove" che vengono fornite ad un processo sono "bianche" (ovvero riempite di zeri), per evitare che un processo possa vedere dati riservati da pagine di memoria deallocate da altri processi.

Va detto che diversi compilatori in modalità "debug" (nonché il "debug allocator" dell'heap manager di Windows) riempiono la memoria non inizializzata (su stack ed heap) di "valori sentinella" (va spesso per la maggiore 0xcc o 0xcd), in modo da consentire di individuare problemi di uso di memoria non inizializzata.

Poi vorrei fare un altra domanda:
come potete vedere, nell'esercizio, passo un vettore di dimensione 5 ad un vettore di dimensione 7. Ok, immagino che compilera' i 5 campi con i valori passatigli e gli altri rimaranno memoria sporca. Ma se io passassi il mio vettore di 5 elementi ad uno di 2? i primi 2 come funzionerebbe?
Qui è la sintassi C che confonde. Quando in una dichiarazione di funzione tu scrivi


void funzione(int arr[50]);

di fatto viene parsato come


void funzione(int arr[]);

ovvero


void funzione(int *arr);

Questo segue dal fatto che, per una serie di accidenti storici, in C gli array (1) non si possono passare per valore e (2) decadono facilmente in puntatori al primo elemento. In effetti, personalmente ritengo che sia buona norma semplicemente non specificare le dimensioni degli array passati come parametri a funzione, visto che sono sempre e comunque puntatori e scrivere delle dimensioni serve solo a confondere i principianti.

(tieni conto comunque che questo vale solo per i parametri delle funzioni, se in una struct o da qualunque altra parte scrivi int arr[50]; stai effettivamente dichiarando un array di 50 interi)


se io so che dentro ci sono 5 elementi, io posso scrivere vettore[5] anche se il vettre è stato dichiarato ti dimensione 1. Giusto?

Sì ma questo è un artefatto di quanto appena detto, ovvero segue dal fatto che, se è un parametro ad una funzione, di fatto è un puntatore. Ovviamente se fai vettore[5] su un vettore che è stato effettivamente dichiarato come di 1 elemento vai in undefined behavior.

BoG
21-12-2013, 19:05
Forse mi sono espresso male...chiedo scusa.
Magari possiamo risolverla così:
Ora commento il codice del programma così mi dite dove sbaglio.


using namespace std;#include <iostream>
void cambia(int m, int n[7]){
(*(n+m))--; //n punta a vet[-1], memoria sporca, m = 4, quindi *(*(n+m)) punta a vet[-1+4] e decrementa il valore presente
m++; //m=4 diventa m=5
n--; //mi sposto nell'elemento precedente del vettore
//ora, siccome, vet-1 è stato passato per indirizzo (perchè gli array sono passati come indirizzi)
//ad un altro puntatore (perchè un array, n[7], è un puntatore) avro' che le mie modifiche saranno
//salvate sul vettore contenuto nel main: vet[]. Invece la mia variabile locale int m;
//non avendo il segno & davanti riceve una copia del valore contenuto in vet[4], quindi le modifiche sono perse.
}


int main(){
int vet[] ={4,2,3,2,4};
cambia(vet[4], vet-1);//passo alla funzione l'indirizzo del 4°elemento del vettore e lo metto in int m;
//poi prendo l'indirizzo della cella di memoria contenente vet, ci sottraggo 1 (cella) e passo questo indirizzo a n[].
//è un po' come se gli avessi passato vet[-1]

cambia(0,&vet[4]);
//ora, chiamo di nuovo la funzione scambia passando un intero e l'indirizzo di memoria &vet[4]. Domanda: se passo come parametro vet[4] passo il valore contenuto in vet[4] oppure l'indirizzo
//della cella di memoria vet[4]? e se avessi passato vet+4? oppure &vet+4? oppure &vet[4]?
//Provo a dare la mia idea:
// vet+4: passo l'indirizzo del primo elemento + 4, quindi passo l'indirizzo del 5° elemento del vettore
//vet[4]: passo il valore contenuto nella posizione vet[4], quindi un intero, nel mio caso =4
//&vet+4: credo valga lo stesso discrorso di vet+4
//&vet[4]: passo l'indirizzo di memoria del 5° elemento dell'array

//in questo caso, l'ultimo elemento dell'array sara' decrementato di 1, perchè passando &vet[4]
//identifico l'ultimo elemento del mio array, gli sommo la variabile m che nel mio caso ha ricevuto
//come parametro il valore 0, poi con (*(n+m))-- decremento l'ultimo elemento del vettore.
//dopo di che fa anche un m++ ma non serve a molto e poi fa n-- e quindi sposta il puntatore
//indietro di un elemento dell'array (nulla di rilevante ai fini dell'alterazione dei valori nell'array).
//siccome si parla ancora di vettori ho che sono stati passati per indirizzo, quindi le modifiche sono state salvate
int i=0;
for(i=0; i<5; i++)
cout << vet[i];
return 0;
}

Giusto?

Loading