C stringkezelés

Fórumok

Sziasztok!
Új vagyok mind a C-ben, mind a HUP fórumán, úgyhogy kérlek nézzétek el, ha baromságokat kérdezek.

A kérdésem a következő:

A C-ben ha sztringet próbálok deklarálni,
az alábbi módon:

char *string;

akkor tulajdonképpen mit csinálok?
Mi garantálja azt, hogy ez a pointer olyan területre mutat, ahol nincs semmiféle más adat, amibe beleírhatnék? Ez a kérdés tulajdonképpen azért merült fel bennem, mert sok példakódban, szakkönyvben használják ezt így, mindenféle malloc() nélkül.

A másik kérdésem, hogy az így deklarált sztringeknél érdekes dolgot fedeztem fel:

char* string1="valami";
char* string2="valami";
char* string3="valami";

Nevezetesen azt, hogy a gcc fogja magát, és nemes egyszerűséggel ugyanarra a címre mutató pointerrekké teszi azokat. Ennek következményeként tkp. csak 1 darab sztringhez jutok, ami nem az amit akartam.

Válaszotokat előre is köszönöm.

Hozzászólások

elso:

char* azt jelenti, hogy egy karakterre mutato mutato. int* meg egy mutato, ami intre mutat. ennyi. malloc nelkul nem szabad hasznalni, mert random ertekre mutat. ja, es free()zd, ha mar nem kell, itt nincs garbage collection.

masodik:

gcc kioptimalizalja, valoszinuleg. valahogy tuti le lehet tiltani, vagy bugos a gcc :) hanyas verzio?

Hálásam köszönöm a gyors választ, a kérdés igazából azért merült fel bennem, mert a

char string[32]="valami";

kódot a gcc visszadobta, valamint, amint írtam, sok helyen láttam malloc() nélkül használt sztringpointert.

a másodikra a válszom az, hogy gcc 4.1 (djgpp) alatt történt a fent említett eset.

Mellesleg, a hupon valami szokas ekezet nelkul irogatni, rengeteg threadban lattam mar ezt.

Ekezetek nelkuli iras hmmm... Szerintem joval gyorsabb, mint ekezetekkel. Tovabba egy kis gyakorlassal rendkivul konnyen dekodolhato, es nem zavaro. Szerintem azok irnak ekezetek nelkul, akik
a, nagyon regota, meg a DOS-os korszaktol hasznalnak szamitogepet es angol billentyuzeten szocializalodtak
b, fejlesztok, rendszergazdak akiknek gyakorlatilag soha sincs szukseguk magyar kiosztasra (bar lattam mar magyar kiosztassal kodolo arcot is)

A masodiknal adjal meg kulonbozo kezdoertekeket, kulonben nem erzekeled a kulonbseget. :-)

---------------------
Minnél korszakalkotóbb ötlettel állsz elő, annál több hülyén kell átverekedned magadat.

Ez azért van mert mind a két string egy pointer, ami a "0" címre mutat (ascii 0 értékére).
Ha erre a címre írsz valamit, akkor másik is ezt látja, mivel oda mutat. Ez a művelet roppant veszélyes!!!! mert valahova írtál, de fogalmad sincs mi volt ott és mit írtál felül.
Helyesen ez lenne
char st1[256];
char *string1;
char *string2;
string1=st1;
strcpy(string1,"blabla");
printf("%s",string2); // zagyva kiirás
string2=string1;
printf("%s",string2); // blabla kiirása

Valami ilyesmire gondoltam:
char *string1="1";
char *string2="2";

Mivel az elozo megoldasnal ahol az osszesnel "valami" volt az kezdoertek, akarmelyiket valasztod egy printf-nel, ugyanaz az eredmeny fog visszajonni (csak egy masik valtozobol). :-)

---------------------
Minnél korszakalkotóbb ötlettel állsz elő, annál több hülyén kell átverekedned magadat.

A C-ben igazából "nincsen" string változó. Ezt egy karakteres tömb helyetesíti. Enne a tömbnek a kezdetére mutató pointerrel kell dolgozni. Egy string végét a 0 jelöli. Így amennyiben nem foglalsz le helyet úgy egy tömb tartalmazhatja fix!! hosszon a stringedet.
Ha szeretnél szöveget konstansként definiálni akkor használd a
char string1[]="Szoveg....";
char string2[]="Szoveg....";
stb...

Ha csak akarsz egy "string" változót csinálni, akkor az előbb leírtak szerint egy char tömbt kell csinálnod.
char string[256]; // ebbe 255 karakternyi szöveg fér, mert a végére 0 is kell
ebbe úgy tusz szöveget írni, hogy belemásolod.
1. lehetőség:
strcpy(string,"Szovegem.....");
2. formázottam:
sprintf(string,"Ide jön a szöveg, meg az egyég báltozók kiiratási módja, pl %d értéke",egy_szam);

A pointerek egy kicsit másak.
Két tulajdonságuk van, a hoszuk, és az értékük. A hosszával csak a fordító foglalkozik, meg persze Te, ígazából sehol nem jelenik meg, de ha a pointerhez hozzáadsz, akkor mindig a hozzáadott érték szer a hosszával nő az értéke. Az értéke meg egy cím ahova mutat.

A string változót (ami egy char tömb) ne keverd egy pointerrel!!!

Egy kicsit hozzáfűzök, nem biztos, hogy hasznos lesz most, de szerintem jó tudni.

Amikor egy stringet pl. így deklarálsz, hogy:
char *valami = "Túró";
akkor azt mondod, hogy a "Túró"-t tárolja le a binárisban, a valami kezdeti értékét pedig tegye a tárolt terület elejére.

Abban az esetben, ha a következőt használod:
char valami[256];
akkor utasítod a fordítót, hogy a binárisban jelezze, hogy félreteszel statikusan 256 byte-ot. Majd ha strcpy-vel, memcpy-vel, stb. Belemásolsz, akkor erre az eleve lefoglalt statikus területre írsz.

Ha pedig ezt használod:
char *valami = (char *)malloc(sizeof(char)*256);
akkor a fordítót utasítod, hogy a bináris tartalmazza a malloc hívást. A malloc pedig utasítja a rendszert, hogy foglaljon le neked 256 char-t.

"i pensieri stretti & il viso sciolto." -- Sir Henry Wootton

Abban az esetben, ha a következőt használod:
char valami[256];
akkor utasítod a fordítót, hogy a binárisban jelezze, hogy félreteszel statikusan 256 byte-ot. Majd ha strcpy-vel, memcpy-vel, stb. Belemásolsz, akkor erre az eleve lefoglalt statikus területre írsz.

a 'statikus' egészen mást jelent, itt azt akartad írni, hogy
char valami[256]; -> a stack-ben lefoglalsz 256 byteot
char* valami=(char*) malloc(sizeof(char)*256); -> a heap-ben foglalsz le 256 byteot
static char valami[256] -> a statikus területre foglalod le a változót.

Brian W. Kernighan and Dennis M. Ritchie, A C programozási nyelv, Az ANSI szerint szabványosított változat, Műszaki Könyvkiadó

Morzel

már mindent leírtak előttem, csak morzsákat teszek hozzá. A


char* ptr;

az egy pointer változót deklarál. Ez egy integer, ami megmondja, hogy a memóriában hányadik bájton kezdődik az adat.
A fenti változó egy karakter címét hordozza.
Mivel a pointert nem inicializáltad, ezért értéke bármi is lehet, vagyis a memóriában random helyre mutat. Ez nagyon rossz gyakorlat, általában előbb-utóbb katasztrófához vezet.

A C-ben a string az karaktertömb, más néven karakterek /byte-ok/ memóriafolytonos egymás utánja, amit nulla byte zár le.
Ezért az első karakter címét elég megadni, hiszen azután ott következik a többi.

Ha valahol ezt írod:


"szoveg"

akkor egy string konstanst hoztál létre. Ezt a gcc beleteszi a binárisba, egy csak olvasható adatterületre. Ha ide írni próbálsz, segfaultot kapsz. A


char* ptr = "szoveg";

létrehoz egy pointer változót /beleteszi egy integer változóba a konstans string első byte-jának címét/.
A fordítók kellemes tulajdonsága, hogy a többször előforduló ugyanlyan string konstansokat csak egyszer tárolják. Hiszen minek is tárolnák többször, ha egyszer úgysem módosítható?

Ezért a


char* ptr  = "szoveg";
char* ptr2 = "szoveg";

eredménye az lesz, hogy a


's' 'z' 'o' 'v' 'e' 'g' '\0'

bájt sorozat csak egyszer lesz benn a binárisban, és mindkét pointer arra lesz ráállítva.

Figyelmesebb szemlélőnek feltűnhet, hogy a pointer változónk deklarációja tulajdonképpen hibás, hiszen a


const char* ptr  = "szoveg";

lett volna a helyes. Ez fejezi ki, hogy a pointer által mutatott adatterület nem módosítható!

Kezdő programozók vért izzadnak, mire megértik, hogy miért fontos a const korrektség. Lamer programozók ismérve, hogy ezt soha nem értik meg, és nem fogadják el. Ajánlom, hogy te a szabályos úton menj.

Más:
a karakter konstansokat ne keverd össze a pointerekkel! Lásd


const char* ptr  = "szoveg";
const char  myszoveg1[] = "szoveg1";
const char  myszoveg2[] = {'s','z','o','v','e','g','2','\0'};

A ptr itt egy önálló életet élő pointer változó. Értéke megváltoztatható, ekkor egy másik karakter vagy string címe kerül bele.
A myszoveg1, myszoveg2 string konstansok címei. Így csupán szimbolikus nevet adunk egy string konstansnak.

Más: ha műveleteket akarun kvégezni stringekkel, akkor átmeneti string kezelő adatterületekre lesz szükségünk. Ezek a nem const karakter tömbök:


char tmps[100];

Ebbe lehet firkálni, belemásolni, appendelni stb a stringeket.
De: a C-s stringkezelő függvények tulajdonképpen teljesen elavultak, mert biztonsági résekhez vezethetnek. Keveredik bennük a fix méretű temporáris stringterület és a definiálatlan hosszúságú nullával lezért string koncepció.
Ha megteheted, nagyobb string kezeléssel foglalkozó rendszert ne C-ben írj! Használd inkább a magasabb szintű c++ string kezelő osztályokat.

pont, hogy nem valtoztathato meg, hiszen const. marmint a legutolso peldaban a const char *ptr (nemtudom mi ez a mania hogy a tipus utan irjak a csillagot...), masreszt a const ANNYIRA nem fontos, max olvashatosagon javit IMHO => kompajler ugyis eszreveszi. meg nem lehet mindig tudni, hogy mi lesz const, mi nem...

const char * string = "almafa";

Itt nyilván ilyet nem lehet: string[0] = 'c';

No de később a kódban:
const char string2[] ="körtefa";
string = string2;

teljesen helyes, hiszen nem a pointer konstans, hanem a pointer által hivatkozott terület

Helyes használat:
const char* const string = "almafa";


Helyes használat:
const char* const string = "almafa";

A contextustól függ, hogy helyes-e a második const használata.
Akkor helyes, ha a string nevű változónk olyan, hogy mindig a "almafa"-ra kell mutatnia, és meg akarjuk tiltani, hogy valaki egy másik konstans stringre állítsa rá. Ekkor viszont felesleges pointer változót használni, erre valók a string konstansok.

lényegtelen, hogy hány bájtos, a 4-et csak a tömörebb szövegért írtam "az adott platformon érvényes pointerméret byte-ban" helyett.

Az inicializált tömb bizony majdnem teljesen úgy viselkedik, mint egy define, a változó viszont nem úgy viselkedik! Tárfoglalása van /+1 pointernyi/, változtatható értéke van, értelmes a címét lekérdezni, használatakor +1 memóriaelérésre van szükség!

példa:


const char szoveg[] = "szoveg";
const char* ptr = szoveg;

int func()
{
   printf("%s\n", szoveg); /* 1 */
   printf("%s\n", ptr);    /* 2 */
}

Az 1-es esetben a lefordított kód valami ilyesmi:


ld eax,0x12345678 ;szoveg[] konstans cimenek betoltese
push eax
call _printf

A 2-es esetben viszont ez:


lea eax,ptr   ;ptr cimenek betoltese
mov eax,[eax] ;ptr-ben levo cim betoltese
push eax
call _printf

teccik latni a +1 indirekciot?

Abban igazad van, hogy pointernek lehet értéket adni, char tömbnek meg nem.

//pointerek.c
int main() {
char szoveg[] = "szoveg";
char szoveg2[] = "szove2";
char*const ptr = "szove3";

puts(szoveg);
puts(szoveg2);
puts(ptr);

puts("====");

2[szoveg]='a';
szoveg2[3] = 'b';
*(ptr+2) = 'a';

return 0;
}

make CFLAGS="-Wall -Werror -pedantic -std=c99" pointerek

és lefordul! Sőt, ansi mellett is. És segfault-ol. No ez már röhejes. Triviális hiba, könnyen kiszúrhatná a fordító.

Ha nem adnék kezdőértéket, nem lenne különbség a változók között.

const char * string = "almafa";

teljesen helyes, hiszen nem a pointer konstans, hanem a pointer által hivatkozott terület

*gyakorlatilag* nem a pointer által hivatkozott terület a konstans, hanem egy const T* -szerű pointeren keresztül nem lehet megváltoztatni a mutatott területen lévő típus értékét. A const egy fordítási időben jelenlévő attribútum (gimme ellenpélda, amikor nem az).

a legtöbb mai architektúrán nem igaz, amit írsz. 386-os procitól vannak a csak olvasható memória szegmensek. Ha ezekbe próbálsz írni, akkor coredump a jutalmad.

A literális string konstansokat és a const változókat a c fordítók általában ilyen adatszegmensekbe pakolják, ezek tehát valóban, fizikailag sem írhatók közönséges pointer castolással.

példa:


const char* ptr = "almafa";

int func()
{
   *(char*)ptr = 'b'; /* core dump! */
}

Hogy ez ne csak futási időben derüljön ki, hanem fordítási idejű támogatásod is legyen, azért létezik a const.

eredmények:
- globális const változóval segfault, tehát így jó
- gcc-vel fordítottam eddig is:)
- string konstanssal is segfaultol

itt van a kutya elásva:
0x0804836b : movl $0x5,0xfffffff8(%ebp)
0x08048372 : movl $0x3,0xfffffff8(%ebp)

Ezek szerint readonly szegmenst csak egyet foglal le, ahova viszont csak globálisan const-nak definiált változókat, meg string konstansokat rak.