Informatică, întrebare adresată de AfloareiAndrei, 8 ani în urmă

Am o intrebare legata de structures in c++.
Am atasat 2 imagini in care este un program simplu (in coltul dreapta-sus este ce afiseaza programul).

Jucandu-ma putin cu structurile de date in c++ am observat in programul asta ca, daca declar 'book1, book2, book3' imediat dupa 'struct book', programul merge bine.
Dar daca declar 'book1, book2, book3' in functia main() ex: Book book1, book2, book3;
In rezultatul final apar niste caractere in plus, dar nu la toate book-urile.

Poate sa imi spuna cineva de ce se intampla chestia asta?
La fel se intampla daca folosesc 'typedef struct'.

Anexe:

VerdeDeParis: Salut. Ia incearca in loc de sizeof() sa folosesti strlen(). De asemenea, ce compilator folosesti? GCC sau MSVC?
AfloareiAndrei: gcc
AfloareiAndrei: strlen() imi arata doar valoare 6 si da, daca il folosesc nu imi mai pune caractere in plus dar imi salveaza doar primele 6 caractere.

Răspunsuri la întrebare

Răspuns de VerdeDeParis
3

Nu bag mana in foc ca asta e explicatia, ar putea sa depinda de compilator, sau sa fiu total pe langa cu ea, dar ma dau cu parerea. Cand declari variabile globale (poza 1), variabilele sunt alocate static in segmentul de date in memorie (zona de memorie se numeste Text/Global, alaturi de Heap si Stack). Desi nu sunt sigur de asta, si NU cred ca e asa, dar e posibil sa fie cazul aici, memoria din Global este goala, plina cu zerouri, asadar stringurile tale initial vor fi goale. Cand copiezi stringul tau in autor si in ce mai copiezi tu, la final oricum va fi pun un null terminator ('\0') care indica finalul stringului si asta va face la afisare sa stie cand sa se opreasca cu afisarea din memorie. Ca regula generala, nu e frumos sa declari variabile globale decat daca e strict nevoie. Functia ta de copiere a stringului nu cred ca e bine facuta, eu cand copiam stringuri in C copiam strlen() caractere, nu sizeof, dar nici eu nu sunt zeu in programare.  

Cand declari o variabila locala (poza a doua), se aloca memoria in Stack, care nu este golita inainte de rulare. Ce se gaseste acolo, dumnezeu cu mila. Stringurile tale vor fi copiate, dar nu cred ca pui bine acel null terminator pe pozitia aia, ti-am zis, eu l-as pune pe strlen-1.  La afisare o sa afisezi pana cand gasesti un null terminator, care era inainte in memorie. Pana la acel null terminator, pot fi o groaza de biti veniti din alte parti pe care calculatorul la runtime ii interpreteaza fortat ca si caractere si incearca sa ii afiseze. Sfatul meu este sa folosesti strlen. Si chiar sa inveti sa folosesti strcpy. Si memcpy. Si invata sa folosesti pointeri.


CinevaFaraNume: "compilatorul goleste memoria din Global si o umple cu zerouri"
CinevaFaraNume: Nu este treaba compilatorului sa faca asta
CinevaFaraNume: E facuta la imediat inainte ca programul sa ruleze
CinevaFaraNume: facuta imediat*
VerdeDeParis: Am scris tampenia aia si acum am vazut. Imi cer scuze.
VerdeDeParis: Acum am vazut ce am spus.
CinevaFaraNume: "compilatorul ii interpreteaza fortat ca si caractere si incearca sa ii afiseze" cred ca faci confuzie intre compilator si calculator
VerdeDeParis: Sunt cu mintea in 7 parti si ineficient azi, dar nu am scuza pentru neglijenta din limbaj
VerdeDeParis: Multumesc pentru vigilenta, am corectat
VerdeDeParis: Evident ca nu avea treaba compilatoru cu ce se intampla la runtime, sunt aiurit rau azi
Răspuns de CinevaFaraNume
2

Problema este in ce sectiune se gasesc variabilele in memorie.

Aici sunt doua posibilitati: sunt variabile declarate intr-o functie(locale) si se gasesc in stack sau sunt variabile globale si se gasesc in sectiunea ".data".

In cazul in care sunt variabile globale, toti bytii din alcatuirea interna sunt initializati cu 0 (deci daca copiezi ceva aici, restul caracterelor vor fi egale cu zero), si cand vei apela operatorul << din cout cu acestea, se va opri la primul caracter nul.

Pe de alta parte, daca sunt declarate in stack, nu au valori exacte, ci doar valori reziduale din memorie care se intamplau sa fie aici, astfel vezi niste caractere(sau semne de intrebare daca nu sunt afisabile), pana cand ajunge la un caracter 0(la fel, rezidual) sau pana incearca sa acceseze o adresa nepermisa, care duce la SIGSEGV.

Problema apare de la faptul ca functia copyString nu primeste marimea corecta a sirului pe care il copiaza, ci marimea alocata sirului in care se copiaza(32 pentru autor si 64 pentru titlu), astfel conditia i < n-1 va fi mereu adevarata(si nu va pune caracterul nul unde trebuie).

Problema asta se rezolva doar prin linia:

n = s.length();

inainte de for.

Acum caracterul nul va ajunge in locul in care trebuie si nu vor mai fi afisate caracterele reziduale din memoria alocata.

Eu recomand sa folosesti

n = min(s.length(), n);

pentru a nu corupe din greseala memoria daca sirul s este mai lung decat ce a fost alocat.


AfloareiAndrei: Legat de functia copyString. Prima data am facut ceva asemanator cu ce ai sugerat, dar daca incercam sa introduc un string de 10 caractere intr-un char array[5] primeam eroarea ***stack overflow. Asa ca m-am hotarat sa introduc caracterele in functie de spatiul alovat de char array[5] in felul urmator. ex: char array[n] toate caracterele pana la n-1 vor fi litere iar n-1 va fi '\0' (null). restu literelor din string nu vor fi inregistrate.
CinevaFaraNume: Standardul C (nu stiu si despre cel C++) spune ca in momentul in care memoria e corupta comportamentul va fi impredictibil, asa ca e mai bine sa te asiguri ca nu se intampla.
AfloareiAndrei: Vreau sa imi cer scuze ca nu am formulat cum trebuie comentariul de adineauri, pentru ca se poate intelege gresit. Nu vroiam sa te contrazic, vroiam sa iti expun motivul pentru care am facut funtia copyString asa cum am facut-o. Ai dreptate, caracterul null nu il pun unde trebuie. Ms mult pentru raspuns si timpul acordat ;)
Alte întrebări interesante