PDA

Visualizza versione completa : [C++] Problemone con puntatori!


Downloader
14-01-2005, 16.24.47
Sto cercando di capire il funzionamento di un programma che stampa byte x byte una locazione di memoria puntata, solo che non ci sto a capi una cippa.


int contenitore=20;
int *puntacont=&contenitore;
unsigned char *pvar=(unsigned char *)puntacont;

void main()
{
clrscr();
cout<<puntacont<<endl;
puntacont++;
pvar=pvar+2;
for (int i=1;i<=4;i++)
{
printf("%X",(int)*pvar);
cout<<endl;
pvar++;
}
getch();
}


In particolare non riesco a capire gli incrementi dati a puntacont e a pvar.

Datemi na mano, tnx! ;)

Downloader
14-01-2005, 18.46.04
L'unica cosa che ho capito perchè è incrementata è pvar nella for, il resto no :(

LoryOne
14-01-2005, 22.40.09
int contenitore=20;

Questa istruzione assegna alla variabile contenitore il valore decimale 20.
Sebbene a prima vista possano sembrare banali, le operazioni che il PC deve compiere per la
semplice assegnazione di un valore ad una variabile non sono affatto sbrigative, anzi.

Prima di tutto c'è bisogno di memoria sia per immagazzinare il dato prima della sua
elaborazione sia dopo, quindi è necessario predisporne un certo quantitativo che risulti

sufficente a memorizzare il valore massimo che esso può assumere a seconda della sua
tipologia in funzione delle operazioni che su di esso vengono effettuate.

In questo caso si tratta di un intero, tipologia che C/C++ considera pesante (ossia che
occupa) 4 byte in memoria.
Una volta conosciuto il quantitativo, esso deve essere allocato e referenziato in modo da prelevarlo con facilità dalla memoria ed eventualmente sostituirlo con un nuovo valore.

E ' qui che entrano in gioco i puntatori e le locazioni in memoria.
Quando si è alle prime armi con questi potentissimi mezzi si è spesso tratti in inganno

pensando che essi siano due entità distinte. In realtà un puntatore è nello stesso tempo un
valore ed una locazione, sebbene si possa gestirlo separatamente anteponendo alla variabile

che costituisce il suo riferimento i caratteri '*' oppure '&'.

int *puntacont=&contenitore;
Qui puntacont è l'entità che ha come valore ciò che si trova alla locazione di memoria
definita da contenitore.
puntacont è di fatto un puntatore ma non ha nulla a che fare con il valore decimale di
contatore poichè:
- ritornerà un valore pari alla locazione di memoria 20
- dovrà necessariamente assumere la stessa tipologia della variabile alla quale fa
riferimento.

Detto questo, è necessario considerare il fatto che (semplificando al massimo) la memoria
può essere considerata come una serie di celle adiacenti l'una all'altra, risultando

quantificabile in base al numero di celle llocate in modo consecutivo all'atto della
definizione del dato che in esse deve essere memorizzato.

puntacont++;
Questa istruzione non aggiunge 1 a puntacont bensì 4 spostando, di fatto, il puntatore alla
locazione puntata da puntacont a puntacont+4.

Thor
14-01-2005, 22.54.10
int contenitore = 20;
int* puntacont = &contenitore;
// puntacont è un puntatore a int, qui posto uguale all'indirizzo della variabile contenitore

unsigned char *pvar = (unsigned char *)puntacont;
// pvar è un puntatore a char, qui uguale a puntacont, precedentemente castato a char* (dato che è un int*

void main()
{
clrscr();
out<<puntacont<<endl;
puntacont++; //come se fosse puntacont+4, te l'ha spiegato LoryOne
pvar=pvar+2; //chiaro, no? abbiamo un char invece di un int, e char è 1byte; come se avessi due pvar++ di fila
for (int i=1;i<=4;i++)
{
printf("%X",(int)*pvar);
cout<<endl;
pvar++;
}
getch();
}

spero non ricordare male! :D

Thor
14-01-2005, 23.21.27
ah..mi sa che c++ non te lo prende, un prog così..il main restituisce int, per esempio..

#include <iostream>

using namespace std;
int contenitore = 20;
int* puntacont = &contenitore;

unsigned char *pvar = (unsigned char *)puntacont;

int main()
{
system("cls");
cout << "puntacont punta a: " << puntacont << endl;
puntacont++;
pvar=pvar+2;
for (int i=1;i<=4;i++)
{
printf("pvar punta a: %X",(int)*pvar++);
cout<<endl;
}
getchar();
return(0);
}

così dovrebbe andare.

Downloader
28-01-2005, 15.12.14
NIENTE! oh non risco proprio a capirli sti cacchio di puntatori!

Cioè non riesco proprio a entrare nella logica, potreste darmi una mano?

Tnx! ;)

LoryOne
28-01-2005, 21.35.53
Ok.
Mettiamo da parte la teoria e facciamo un po di pratica con semplicissimi programmi.
I seguenti esempi devono essere integrati con quello che ti ho spiegato in teoria


#include <stdio.h>
#include <conio.h>

main(void){
int num; //num è un valore di tipo intero
int *punt; //punt è un puntatore di tipo intero

num=1; //a num assegno il valore 1
punt=&num; //punt punta alla locazione in memoria '&' di num.
//La prima istruzione ha dovuto predisporre
//spazio in memoria per contenere il valore
//di num e punt fa riferimento a quella locazione

(*punt)++; //il valore '*' di ciò che punta num deve essere
//incrementato di una unità.

printf("%i",num); //Ora num grazie a punt vale 2
getch();
}



#include <stdio.h>
#include <conio.h>

main(void){
int num[2],val; //num è un array di tipo intero di 3 elementi
int *punt; //punt è un puntatore a num[]

num[0]=1; //assegno i valori agli elementi di num
num[1]=2;
num[2]=3;

punt=&num[0]; //punt punta alla locazione '&' del primo elemento
//dell'array num[].

punt++; //Mi sposto in memoria sul secondo elemento
//di num[]. Non c'è bisogno delle parentesi perchè ogni
//elemento è già formattato opportunamente grazie alla
//dichiarazione iniziale

val=*punt; //Assegno a val il valore puntato da punt
printf("%i",val); //Ora val è uguale al 2° elemento di num[]
getch();
}



#include <stdio.h>
#include <conio.h>

main(void){
char s[]="Downloader"; //s è una stringa di caratteri
char *punt;

punt=s; //punt punta alla locazione del primo
//carattere della stringa s. (s[0])
//Non c'è bisogno di '&' con i caratteri

punt+=2; //Mi sposto sulla locazione del 3° elemento
//di s (una stringa è un array di caratteri)

*punt='u'; //Assegno il valore 117 (ASCII di 'u')
//alla locazione puntata da punt

printf("%c",*punt); //Trasformo in carattere il valore puntato
//da punt
printf("\n%s",s); //S ora è uguale a 'Dounloader'
getch();
}


Il concetto dovrebbe essere chiaro.
Devi sempre stare attento alla tipologia dei dati che devi gestire con i puntatori, soprattutto con i vari +/++ e -/--.
Un intero pesa 4 byte ed il minimo quantitativo di memoria che il computer può spostare è pari ad 1 byte. Questo implica che il computer predispone 4 celle ognuna da 1 byte per contenere un intero.
L'utilizzo di ++ senza parentesi sposta l'attenzione del computer all'indirizzo dell'elemento successivo a quello puntato ma non significa che ne incrementi di 1 il valore; esso viene ricavato in base ai valori dei 32 bit contenuti nelle 4 celle predisposte a contenerlo.

Quando si utilizzano i puntatori valgono le seguenti due regole:
1- Ogni volta che viene incrementato un puntatore, questo punterà all'indirizzo di memoria dell'elemento successivo del suo tipo di base.
2- Ogni volta che viene decrementato un puntatore, questo punterà all'indirizzo di memoria del'elemento precedente del suo tipo di base.

Il discorso si fa più semplice con i caratteri in quanto il loro valore può al massimo essere contenuto in una cella da 1 byte.

Downloader
28-01-2005, 21.51.50
Intanto grazie per l'aiuto, non ho ancora letto quello che hai scritto perchè ho finito di fare un programmino che ti prego di visionare, spero sia corretto.


/*
Testo: Dati un numero intero di tipo long, scrivere il programma che calcola e visualizza
un secondo numero intero (long)ottenuto dal primo invertendone l'ordine dei bytes

*/

#include<iostream.h>
#include<conio.h>
#include<stdio.h>

void main()
{
clrscr();

long cont;
long cont2;

unsigned char *puntalong;
unsigned char *puntalong2;

puntalong=(unsigned char *)cont;
puntalong2=(unsigned char *)cont2;

cout<<"Inserisci il valore: ";
cin>>cont;

puntalong=puntalong+4;

for (int i=0;i<4;i++)
{
puntalong2=puntalong;
(puntalong)--;
(puntalong2)++;
}
cout<<"Il valore inserito con byte spostati Š: "<<(long)puntalong2;
getch();
}

Dav82
28-01-2005, 22.01.24
Mh... non ho sottomano un compilatore C, ma direi che non funziona.

Un long occupa, se non sbaglio (e penso di no, visto il tuo codice), 32 bit, ovvero 4 byte.

Quindi il puntalong punta al primo dei quattro byte di cont, e può "saltare" di byte in byte con gli operatori ++/--, visto che il suo tipo è *char (char occupa un byte).

Cosa bisogna fare per poter poi, con un cast a long, leggere i 4 byte nell'ordine inverso?

tip: devono essere messi in sequenza in ordine inverso :)


Se poi vedi, nel ciclo for, tu assegni ogni volta a puntalong2 un valore secondo il valore di puntalong, e poi modifichi sia uno che l'altro... non ha molto senso, tanto vale incrementare/diminuire subito di 4 e l'effetto è lo stesso. Perchè l'effetto è lo stesso? Perchè ogni volta fai puntare i due puntatori a differenti indirizzi di memoria, ma non modifichi il contenuto della memoria stessa.

Lory saprà sicuramente essere più chiaro di me, che sono Java-minded ;)

LoryOne
29-01-2005, 13.16.04
Per risolvere l'esercizio te ne propongo altri due che ti faranno comprendere meglio come risolvere il tuo che, devo dire, è un ottimo esercizio (Y).

Primo esercizio:
Dato un numero=123456789 (0x499602D2) , ottenere il seguente risultato:
D2
02
96
49

Secondo esercizio:
Dato lo stesso numero, cambiare il secondo byte con 0xF5 in modo da ottenere il seguente numero in notazione esadecimale: 49F502D2

Di seguito è riportata la soluzione all'esercizio che hai proposto ma prima esegui i primi due esercizi.


#include <stdio.h>
#include <conio.h>

main(void){
int numero,nuovonumero;
unsigned char *punt,*nuovopunt;

numero=1234567890;
punt=(unsigned char*)&numero;
nuovopunt=(unsigned char*)&nuovonumero+3;

while (*punt){
*nuovopunt=*punt;
punt++;nuovopunt--;
}printf("Numero impostato : %X",numero);
printf("\nNumero invertito : %X",nuovonumero);
getch();
}

Gergio
01-02-2005, 14.53.32
[un po' in ritardo]
spero tu abbia risolto i tuoi problemi con i puntatori, cmq qui (http://www.elet.polimi.it/upload/bolchini/didattica/linguaggioc/lez10.php) trovi una spiegazione chiara

hth

Downloader
01-02-2005, 17.53.46
Originariamente inviato da gergio
[un po' in ritardo]
spero tu abbia risolto i tuoi problemi con i puntatori, cmq qui (http://www.elet.polimi.it/upload/bolchini/didattica/linguaggioc/lez10.php) trovi una spiegazione chiara

hth

Ti ringrazio per il link che stasera sicuramente leggero, si, credo di aver risolto i miei dubbi perchè oggi mi sono fatto rispiegare l'argomento a scuola, ovviamente la pratica dira se ho capito davvero.

Intanto oltre a te ringrazio Lory, Thor e Dav per il loro preziosissimo aiuto ;)

Fast-M
21-02-2005, 12.20.34
Se può esserti ancora utile, ti scrivo che in realtà non c'è differenza tra una variabile normale di tipo numerico e un puntatore.
Cosa cambia se in una variabile che hai chiamato pippo ci vai a mettere dentro un valore che rappresenta la tua età oppure un valore che rappresenta un indirizzo di memoria?
Nulla.
E' sempre una variabile che contiene un valore.
La differenza è solo nel fatto che una variabile puntatore contenendo un indirizzo di memoria(esempio: FF10BC22 in esadecimale a 32 bit) appunto è come se simbolicamente puntasse quella precisa posizione di memoria rappresentata dal valore stesso che contiene.
Da quì il termine puntatore, perchè appunto è una variabile che contiene un valore che si riferisce o "punta" ad un altro indirizzo di memoria.
:)

ceccus
21-02-2005, 15.39.46
Salve,
Beh....non sono molto d'accordo su questa definizione : "...in realtà non c'è differenza tra una variabile normale di tipo numerico e un puntatore."
Primo perchè le variabili di tipo numerico possono essere "grandi" a partire da 2 byte fino a rifinire a 32 bytes.....e un puntatore è sempre "grande" 4 bytes (nella IA-32)...
Secondo perchè l' indirizzo contenuto in una variabile puntatore DEVE essere valido, pena il molto probabile crash dell'applicazione.
Poi, dal momento che sono un "tipo particolare", sono gestiti in modo particolare , teso a massimizzarne le prestazioni.
Per tutte queste ragioni, accostare una normale variabil enumerica ad un puntatore è quantomeno azzardato...almeno secondo il mio pensiero...

Ciao !!

Downloader
21-02-2005, 15.50.54
Grazie anche a fast-M e ceccus, per il loro aiuto.

Per fortuna ho stracapito tutto, e in tutta sincerita mi darei le testate da solo per non aver capito prima una cavolata simile :D

ceccus
21-02-2005, 15.54.34
Ciao,
Di niente....ci mancherebbe.....

Ciao !!!

Fast-M
22-02-2005, 01.05.57
Certo Ceccus, hai aggiunto dettagli a quello che avevo iniziato a trattare, ma io li avevo tralasciati per il bene della didattica verso una persona che sembrava non aver ben capito il concetto di puntatore.
Da questo capirai perchè nonostante io venga dal basso livello e dal reversing e sarei potuto anche arrivare a trattare l'indirizzamento a 32 bit, l'allocazione della memoria nell'architettura x86, address bus, data bus,raising e falling edge della cpu, ho preferito tralasciare questi dettagli perchè mi sembrava prioritario far capire i puntatori a Downloader anzicchè passare in rassegna le mie conoscenze.
Tanto se capisce i puntatori che in genere rappresentano uno scoglietto per molti, agli altri concetti se non è già arrivato, ci arriva presto.
Sono contento che tu abbia capito i puntatori!
Di niente Downloader, dovere!
:)

ceccus
22-02-2005, 08.28.45
SAlve,
Ok, va bene la didattica, però, a mio avviso, accostare i puntatori a "semplici" variabili numeriche è concettualmente sbagliato....
Poi, sono d'accordo con Te che potremo , forse, stare a parlare ore e ore di come si utilizzano ii puntatori, di come si alloca la memoria (non solo nei PC , ma anche nei sistemi Mainframe, dai quali io provengo....)ecc.......

Ciao !!

Fast-M
23-02-2005, 16.25.43
Ok, accetto il tuo parere. :)
Ma comunque mi diresti che sbaglio se scrivessi:
"Un puntatore è un tipo particolare di variabile numerica concepita per la memorizzazione di indirizzi di memoria, quindi ha delle caratteristiche imposte dall'architettura. Nel caso degli attuali sistemi con indirizzamento a 32 bit, ha un size fisso di 4 bytes, cioè 32 bit perchè tanti ne occorrono per indrizzare appunto 2^32 locazioni di memoria nell'attuale modalità di indirizzamento che è di tipo Flat.
Secondi me, non saresti così convinto di dirmi che ho appena scritto una cosa sbagliata sui puntatori.
Questo sono, Ceccus.
Hanno dei condizionamenti e su questo ci siamo, ma restano sempre al livello sia fisico che logico delle variabili in cui vai a memorizzarci dentro un numero.
Che poi questo numero abbia un size di 4bytes(o più, dipende dal parallelismo) e che questo numero sia guarda caso un indirizzo di memoria è solo un dettaglio in più.
Quindi il mio parere è che non è sbagliato dire che:
"Un puntatore è una variabile numerica con delle caratteristiche particolari".
Questo vuol dire che, ragionando in termini logico/insiemistici, se prendi l'insieme delle variabili numeriche questo conterrà sicuramente un sottoinsieme dei puntatori. Quindi, il puntatore eredita la proprietà di essere una variabile numerica.
:)

ceccus
24-02-2005, 12.11.14
Salve,
Ok...altrimenti rischiamo di andare "fuori tema"....
Tieni presente almeno queste 3 cose :
1) un puntatore, per sua natura, non può contenere un intero (inteso come locazione di memoria) superiore al quantitativo di memoria correttamente gestibile dal sistema in uso, pena un inevitabile crash dell' applicazione se non , addirittura, di tutto il sistema.
2) La gestione dei puntatori è totalmente differente dalla gestione di una qualsiasi variabile numerica definita in memoria (e qui ci sarebbe da dire molto su come un puntatore è gestito a livello di Stack e di Heap)
3) Un puntatore può assumere un valore particolare detto NULL, il quale sta ad indicare che quel puntatore non punta a niente.....cosa che una variabile numerica non può fare...che poi si esplica in un numero....ok...tale numero vale, solitamente, X'FF000000')

Ciao !! :) :D

Fast-M
24-02-2005, 23.24.49
Eh cosa pensi che possa fregargliene di tutti questi dettagli avanzati ad una persona che deve e vuole capire il concetto di puntatore? Li capirà dopo, se ne avrà voglia e se dovrà sbatterci la testa in modo pratico non teorico.
Come ti ho già scritto prima, a queste conosenze se vorrà e avrà la voglia ci arriverà da se e non temere per lui che se vorrà ci arriverà!
Un puntatore che in architettura x86 attuale di tipo flat è composto da 32 bit, può contenere il valore numerico massimo corrispondente a 2^32-1 nel sistema decimale. Che poi vai ad aggiungere che se nella fattispecie l'indirizzo più alto della memoria è inferiore a 2^32-1 e quindi se lo indirizzi va in crash il sistema credo che sia una cosa abbastanza ovvia e che soprattutto non va di certo trattata in questa circostanza.
Magari questi problemi Downloader se li porrà un giorno se avrà a che fare un pò di più con il basso livello e la memoria e credimi che se ha capito i puntatori, tutto quello che hai aggiunto tu lo capirà in un paio di orette di lettura serena e rilassata di un buon tutorial.
Per il secondo punto dico che ancor di più ti sei spinto in modo esagerato di gran lunga oltre quello che era necessario a far capire i puntatori a Downloader.
Cioè, sei arrivato addirittura a trattare di stack e di heap, quando lo stack all'atto pratico potrà servirgli solo se deciderà di addentrarsi nel reversing o nell'assembly e dovrà combattere con continue creazioni di "stack frame" tra PUSH, POP e RET.
Persino nel C che conserva tutto del basso livello si usano in genere le funzioni con il passaggio di parametri che esonerano il programmatore dal noioso compito di memorizzare con dei PUSH in una zona di memoria gestita in modalità LIFO(last in-first out da cui il termine stack, cioè pila) tutto lo stato delle variabili per poi ripristinarlo con dei POP all'uscita di ogni sub...e poi comunque a parte tutto...ma cosa centra con la domanda che Downloader aveva posto inizialmente??
Hey!...
...Ma chè dobbiamo mettere in gioco lo scibile informatico umano per far capire ad un povero ragazzo che cavolo sono sti puntatori?!
Daaai...facciamo le persone mature! :D

Comunque il dibattito è stato bello e utile perchè abbiamo messo in campo parecchi dettagli che possono interessare e rinfrescare le idee a parecchi.
Ciao ceccus, bella!
:D
PS: Ammetto comunque che il valore esadecimale del NULL lo avevo dimenticato!
Ma mi sa che se nel MASM o nel TASM scrivi NULL te lo accettano.
:D

ceccus
25-02-2005, 07.57.43
Salve,
Beh....che dire....
Personalmente sono convinto che per essere "un buon pilota" bisogna conoscere anche (e soprattutto) il funzionamento del motore.....come per essere un buon informatico , a mio avviso, bisogna conoscere cosa gira "dietro le quinte"....
E non credere che , conoscere come funzionano certi meccanismi, nella vita lavorativa quotidiana , non ti aiuti....tuttaltro....anche se dipende molto dall'ambiente di lavoro.
Per il resto....può darsi anche che non sia stato "maturo" abbastanza (che vuoi a 38 anni con 23 anni sulle spalle a cercare di "comprendere" questi aggeggi....), però, chi proviene da ambienti Universitari e soprattutto dal mondo Mainframe, è portato per sua natura a sviscerare i problemi (altrimenti non vai avanti...)
Credo comunque che la discussione sia stata molto utile....

Ciao !!

P.S. : è altrettanto ovvio che in qualsiasi linguaggio di alto livello, se scrivi NULL viene correttamente interpretato.....è il compilatore che traduce il NULL in X'FF000000' (ovvero -1)....questo è vero anche per le ultime versioni del compilatore Assembler del "mitico" OS390.....

Fast-M
25-02-2005, 12.18.58
Scherzavo Ceccus!
Il termine "maturo" che ho usato era solo per gioco e comunque anche io ho 32 anni e sono su questi aggeggi da quando ne avevo 9, età in cui scrissi il mio primo programma in basic che era un loop della stampa del mio nome. Lo so che fa ridere, ma come inizio a 9 anni non credo sia male!
:D
Per quello che hai detto sul discorso del pilota io sono perfettamente daccordo con te ed è proprio questo che mi spinge sempre a voler conoscere ogni minimo dettaglio che sta dietro le quinte e spesso sono anche deriso e criticato per questo. Pensa che spesso nell'ambiente lavorativo con i colleghi ci sono scontri perchè io mi ostino a chiamare le variabili reference "puntatori all'oggetto", mentre altri anche anche se sanno che sono dei puntatori le chiamano con il nome dell'oggetto puntato e io non sono daccordo!
Ti ho detto questo giusto per farti capire che io sono completamente dalla tua parte e sono fissato almeno quanto te con la coerenza alle cose per come sono fisicamente e non per come conviene sintetizzarle, ma oggi la complessità del settore è diventata così immensa che se non si ricorre alla suddivisione teorica in layers, si corre il rischio di aumentare a vuoto la complessità di un settore che è di una vastità smisurata e che praticamente cresce con il tempo in un modo quasi esponenziale.
Il tentativo abbastanza riuscito direi della odierna programmazione ad oggetti, di simulare una "comprensione concettuale" nel pc proprio per facilitare l'interazione con l'uomo la cui mente riesce principalmente ad elaborare le informazioni per idee e concetti, ha come scopo primario propio questo: mascherare il più possibile al programmatore il modo digitale che ha il pc di gestire le informazioni.
In questa ottica credo che non sia certo utile "abboffare" di dettagli tecnici la persona che cerca di acquisire i concetti di fondo della programmazione.
Tanto una volta acquisiti se avrà voglia e passione, non ci metterà molto ad apprendere tutto quello che tu o io abbiamo appreso in modo quasi spontaneo in decenni di interazione con questi strumenti.
Per ora è più importante che apprenda i "puntatori" come idea generale, quasi come se facesse un disegno lineare e pulito nella sua mente.
Più nozioni aggiungi in questo processo, meno questo disegno mentale sarà pulito e chiaro a vedersi, quindi meno immediato.
Questo è il mio parere.
:)

P.S. Quando ho citato Masm e Tasm mi riferivo proprio ai più conosciuti assemblatori per ambiente x86 e cioè Microsoft Macro Assembler(mitico) e Turbo Assembler. Per i linguaggi ad alto livello era ovvio che riconoscessero NULL come keyword, figurati!
:)