C++ láma: const char*

Fórumok

C++-ban kellene kódot írnom (hobby), de még a C sem az erősségem.

Próbálom megérteni, hogy elméleti alapon miért rossz a C++ következő kód:

const char* fix_string = "FIX STRING";
char* szoveg = fix_string;

Nem értem, hibás ez? Amit mondani akarok vele, hogy van egy szöveges konstansom, ami sohasem változhat, és van egy másik szöveges változóm, ami felvehet tetszőleges értéket, például a fix értéket is.

Hol rontom el a gondolatmenetemet?

Hozzászólások

Szerkesztve: 2023. 01. 02., h – 12:27

Nem a fix_string pointer konstans, hanem amire mutat. Tehát fix_string[0]='a' nem jó, viszont szoveg[0]='a' igen. Még azt tegyük hozzá, hogy a 'char *' csak egy pointer, tehát az értékadásod nem stringet másol, hanem csak egy pointert állít át.

Lehet, így írnám inkább:

const char fix_string[] = "FIX STRING";

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

Szerintem is csak ilyen egyszerű, nem kell a *. Igazából a szögletes zárójelek elhagyásával is működnie kéne.

Windows 95/98: 32 bit extension and a graphical shell for a 16 bit patch to an 8 bit operating system originally coded for a 4 bit microprocessor, written by a 2 bit company that can't stand 1 bit of competition.”

Igazából a szögletes zárójelek elhagyásával is működnie kéne.

Lehet, hogy a compiler rájön, hogy nem egyetlen karakterről van szó - bár nem tudom -, de mindenképp értelemzavaró, ha karakter típusnak van deklarálva valami, ami egy karakterre mutató cím. Szóval én akkor is kiírnám a szögletes zárójelet, ha véletlenül nélküle is lefordul.

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

Szerkesztve: 2023. 01. 02., h – 12:54

Ezzel az a baj ogy a fix_string altal mutatott adatot ("FIX STRING") a linker olyan teruletre helyez(het)i ami csak olvashato. Ezt hivjak .rodata szegmensnek. Ha csinalsz egy `char* szoveg` pointert, ami utana bele mutat ebbe a teruletbe, akkor a fordito azt hiheti hogy ez a terulet modosithato. Azaz a szoveg[0]='a' egy szep combos segfault-hoz vezethet (vagy mikrokontrolleren egy bus error-hoz, extrem esetben meg a periferia-busz is mas, pl egy harvard architekturan ez const char* altal mutatott string mehet a progrmem-be, mig a sima char * a RAM-ba mutat).

Olyannnyira igaz ez hogy eleve a string mint tipus meg sima mezei C-ben is const char * tipusu lesz, meg ha ki sem irod:

apal@laptop:~$ cat x.c
#include <stdio.h>

char * fix_string = "FIX STRING";

int main(void)
{
 char * szoveg = fix_string;
 szoveg=fix_string;
/* szoveg[0]='a'; */
 printf("%s\n",szoveg);
 return(0);
} 
apal@laptop:~$ gcc -o x x.c
apal@laptop:~$ ./x
FIX STRING
apal@laptop:~$ 

verzusz:

apal@laptop:~$ cat x.c
#include <stdio.h>

char * fix_string = "FIX STRING";

int main(void)
{
 char * szoveg = fix_string;
 szoveg=fix_string;
 szoveg[0]='a';
 printf("%s\n",szoveg);
 return(0);
} 
apal@laptop:~$ gcc -o x x.c
apal@laptop:~$ ./x
Segmentation fault
apal@laptop:~$ 

Es akkor itt jonnek szep sorban a warning/error szintek:

 - naivan, alapertelmezesben C nyelv eseten a fenti pelda nem ad forditasi warningot/hibat

 - ha a const-ot kiirod a fix_string ele, akkor mar a C forditasi warningot ad (azaz szol hogy valami potencialis gaz lehet, es gaz is lesz mert segfault)

 - a C++ eseten ez meg mar nemhogy warning hanem error, az megerosebben tipusos

Teljesen jó, amit írsz, csak erről eszembe jut, mindig roppant bizonytalan vagyok abban, ilyenkor a string vagy a pointer lesz read only, illetve hogyan kell leírni azt, ha a másikat szeretném.

char *p

const char *p

char const *p

const char const *p

Ezeknek egyáltalán van így értelmük?

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

A const a C++-ből származik, de ennek ellenére nem hülyeség. Gondok azért vannak vele, lásd pl. itt: http://yosefk.com/c++fqa/const.html

By the way, not using const in C++ is quite likely not a very good idea. Of all questionable C++ features, const probably does the most visible damage when avoided.

Én nem értek a C++-hoz, de C-ben nem olyan bonyolult:

const char *p - konstans karakterre mutató változó pointer

char * const p - változó karakterre mutató konstans pointer

const char * const p - konstans karakterre mutató konstans pointer

lejjebb linkelték a cdecl.org-ot, ott jól lehet ilyenekkel játszani. A fenti példáidban az utolsó 3 ugyanazt írja le, a legutolsóban a második const felesleges.

Tulajdonképpen logikus, hogy az első esetben a dereferált érték a const char, a másodikban a pointer const, ami char-ra mutat, míg az utolsóban const a pointer, majd a dereferált char típus is const. Így látva megvan, de szinte sohasem használta, így fejből nem ment volna, mint ahogy nem is ment. :(

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

Akkor picit pontosítok a kérdésen, mert úgy tűnik, a fenti meglátások mind azt magyarázzák, miért jó, hogy hibát ad a fordító, és nem azt, hogy egy ilyen problémát hogyan kellene megfogalmazni c++ alatt.

Tehát szeretnék definiálni egy konstans stringet, vagyis azt akarom, hogy sem a változó által mutatott cím, sem a string tartalma ne változzon. Ha jól értem, a

const char* FIX="fix";

ezt definiálja. Ha az elejéről a const elmarad, akkor a FIX pointer értéke változhat, de maga a szöveg akkor sem.(?)

De hogyan tudom ezt korrekt módon értékül adni egy olyan változónak, aminek ez - mondjuk csak - a kiindulási értéke kell, hogy legyen, de később kaphat majd más értéket is.

A

char* valtozo = FIX;

kódra fordítási hibát kapok. Nem tudom, a fenti igényt hogyan fogalmazhatnám meg úgy, hogy a fordító le is fordítsa.

Vagy ezt a kifejezést nem lehet a c++ alatt helyesen megfogalmazni?

Igazából ez az egész itt olyan, mintha azt kérdeznéd, hogy milyen a korszerű gőzmozdony. A válasz az, hogy semmilyen, nincs korszerű gőzmozdony.

Tehát ha C++-t akarsz, akkor a string típus a barátod; ha C-t, akkor már jogos a kérdés:

char *valtozo= strdup(fix);

vagy például, ha hosszabb szövegnek akarsz fix helyet foglalni:

char valtozo[1024];
strcpy(valtozo, fix);

1 - Továbbra sem értem, hogy elméleti alapon miért rossz, hogy egy konstans területen lévő értéket használni akarok. A gőzgép elavult, de egy fix érték felhasználása a programkódban nem hiszem, hogy elavult lenne. A C++ - úgy tudtam - a C korrektebb, teljesebbé tett változata. Jobban véd, hogy struktúrálatlan programot írjak, így biztosabb lesz a kód. De a konstans érték felhasználása nem biztonsági kockázat. Semmilyen elvi akadálya nincs, hogy ezt értelmezni lehessen. Nem hibaforrás. Ennek ellenére nem hiszem, hogy csak azért ad rá hibát, mert nem programozták le a fordítóban, hogy ne adjon.

2 - Ha ezt mégis csak valamilyen String objektummal lehet megoldani C++ alatt, akkor az hogyan néz ki?

> Továbbra sem értem, hogy elméleti alapon miért rossz, hogy egy konstans területen lévő értéket használni akarok.
> A gőzgép elavult, de egy fix érték felhasználása a programkódban nem hiszem, hogy elavult lenne.

Olvasni lehet. Lemásolni lehet. Felülírni nem lehet/szabad/illik. (Platformfüggő, hogy mi történik.)
Talán az nem ment át, hogy egy pointer-értékadás, az nem a string (karaktereinek) másolása, csak egy pointer ráállítása ugyanarra a stringre.

> A C++ - úgy tudtam - a C korrektebb, teljesebbé tett változata. Jobban véd, hogy struktúrálatlan programot írjak, így biztosabb lesz a kód.

Igazából egy teljesen külön nyelv, amelynek a legfőbb tulajdonsága, hogy mérhetetlenül bonyolult és nehezen tanulható, viszont 30+ éve intenzíven változik, mondhatni gyerekkorát éli.

> De a konstans érték felhasználása nem biztonsági kockázat. Semmilyen elvi akadálya nincs, hogy ezt értelmezni lehessen. Nem hibaforrás. Ennek ellenére nem hiszem, hogy csak azért ad rá hibát, mert nem programozták le a fordítóban, hogy ne adjon.

Olvasni lehet. Lemásolni lehet. Felülírni nem lehet/szabad/illik. (Platformfüggő, hogy mi történik.)
Talán az nem ment át, hogy egy pointer-értékadás, az nem a string (karaktereinek) másolása, csak egy pointer ráállítása ugyanarra a stringre.

Az előbbi kettőhöz adok még egy példát:

char varstr[]= "Kezdeti ertek, felulirhato";

> Ha ezt mégis csak valamilyen String objektummal lehet megoldani C++ alatt, akkor az hogyan néz ki?

Pl.:

const string fix= "Fix";
string var= fix;

char varstr[]= "Kezdeti ertek, felulirhato";

*felülírható maximum ugyanannyi bájttal, mint az eredeti karaktertömb volt és figyelni kell a \0-ra a végén,

https://stackoverflow.com/questions/9593798/proper-way-to-copy-c-strings

 

De érdemesebb használni az std::string-et C++-ban annak, akinek fogalma sincs a pointerekről.

A C fix stringek csak olvasható területen vannak.

A "FIX STRING"-et nem tudod átírni!

Nézzük a következő példát:

char *strcpy(char *dest, const char *src)

Sima char* előléptethető const char-á, tehát az src lehet const char* is meg sima char* is. Ellenben a dest SEMMI ESETBEN sem lehet const char*, csak char*.

Azért nem tudod sima char*-ra megadni, hogy rámutasson, mert a sima char* viszont már írható területre mutat. És így pl. be tudnád adni a szoveg változódat a dest helyére, viszont ha a szoveg egy csak olvasható területre mutat, akkor már meg is van a baj.

szerk: viszont a fix_string-ben nem a pointer a konstans, hanem a mutatott terület. Annak nincs akadálya, hogy később kiadj egy ilyen utasítást:

fix_string="MÁSIK FIX SZÖVEG";

Ezt simán meg fogja enni.

Az is működik, hogy a szoveg is const char*.

És az is működik, hogy pl. a fenti strcpy-val lemásolod a fix_string-et a szoveg-be. Természetesen előre allokálva neki területet.

szerk2: a char*-ot jó lenne a C++-ban elfelejteni. Sajnos tudom hogy nem lehet mindenhol kiküszöbölni, de akkor minek van az std::string?

szerk3: ne keverd a magyar és az angol változóneveket :)

De a konstans érték felhasználása nem biztonsági kockázat.

Nem az. Hasznald egeszseggel ;) De a "felhasznalas" (adathoz valo hozzaferes) meg a "feluliras" (adat modositasa) ket kulonbozo dolog.

A stringek meg ugy altalaban a konstans tombok kezelesnek a modja az 4 fele lehet, es mind a 4 fele modozat az letezik a gyakorlatban. Azaz vagy van memoriavedelem a (operacios/architekturalis) rendszerben vagy nincs, es vagy hardvard-szeru az architektura, vagy egy adatbuszon log mindenki. Peldak ezekre:

  • nincs memoriavedelem + egy adagbusz van: ilyen a klasszikus DOS: csinalhatsz sztringeket, el is ered, oke, nyugodtan felul is irhatod es akkor felul lesz irva latvanyos kovetkezmenyek (vagy ha ugyes vagy, semmilyen kovetkezemey nelkul). De ugye ismerjuk hogy a DOS milyen, milyen volt... 
  • nincs memoriavedelem de tobb adatbusz van: ilyen pl az AVR rendszer. itt mar figyelni kell hogy mi micsoda, a forditonak nagyon tudnia kell mit csinalsz (mire gondol a kolto), mert mas gepi kodu utasitas eri el a stringeket mint a sima mezei char xyz[...] tomboket (azaz nemcsak tobb adatbusz van, de tobb cimtered is van).
  • van memoriavedelem + egy adatbusz van: ilyen a klassizkus UNIX/Linux rendszer, ezt ismerjuk ilyenkor mi a kovetkezmeny (virtualis lapok vannak, amiket az op.rendszer leved, readonly modon (r--) lehet csak a .rodata szegmenst kezelni, a const whatever * tipusok oda mennek, stringek oda mennek alapbol, stbstb).
  • van memoriavedelem + tobb adatbusz van: ilyenek peldaul a Cortex-M magok, amik bus errorral vagy hasonlo modon reagalnak ha ezt az egeszet nem jol csinalod (pl a fenti nagyobb peldat ha egy Cortex-M4-on forditod akkor az egy bus exception-ba fog belerohanni, nem segfaultba). Az mas kerdes hogy ezt mennyire hiv(hat)juk memoriavedelemnek, de ezeknel a magoknal ez mar megjelenik - es persze az "adatbusz" is kicsit viszonylagos, mert vannak mindenfele egyeb hardverelemek menetkozben (arbitrerek, bus matrix-ok, stb). Mondjuk azt hogy "egy cimter van de tobb adatbusz".

A lenyeg a lenyeg hogy ha ezt az egeszet jol absztrahalod, kello tampontot adsz a forditonak es/vagy betartod a sztenderdeket akkor olyan C/C++ kodot kapsz, ami mind a 4 fenti alap-architekturan szepen es biztonsaggal fog mukodni. Ha nem, akkor lehet hogy latszolag jo lesz (pl megfelelo trukkozessel, cast-olassal, void *-os jatekokkal, stb ki tudod kerulni a warningokat/errorokat), de nagyon konnyen beleszaladsz egy pofonba, akar sokkal kesobb is. Es nehez kidebuggolni hogy mi a baj.

Ez ilyen vak vezet világtalan lesz, mert én is sokszor kipróbálom, amikor ezekre szükségem van.

Egyrészt nem mondtad, mi az a fordítási hiba. Írd le a hibaüzenetet! Másrészt nem mindegy, hogy a string egy read only területen, például flash memóriában tárolt karakter lánc, amelyre mutat egy RAM-ban tárolt, így felülírható pointer, vagy a pointer az, ami nem változtatható meg.

char* valtozo = FIX;

szerintem azért problémás, mert a pointereknek van típusuk, nem void * az összes. Tehát, ha egy olyan típusú pointernek adsz értéket, ami azt tudja magáról, hogy az általa címzett memóriatartalom írható, olvasható, s egy olyan címet kap értékül, amelyen lévő tartalom nem írható, akkor a fordító szorongani kezd, s attól tart, hogy a későbbiekben csinálsz valami ilyesmit:

*(valtozo + 2) = 'A';

Szerintem lehetőséged van erre:

const char *valtozo;

valtozo = FIX;
valtozo++;
printf("%c\n", *valtozo);

Vagy csinálhatod ezt:

char *valtozo;

valtozo = (char *) FIX;

Csak ez utóbbi esetben úttörő becsszóra ne próbálj *valtozo-nak értéket adni, mert abból tényleg segfault lesz.

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

Látom akinek csak kalapácsa van... :)

Egy szóval nem írta, hogy ez mikrokontroller lenne, meg C++ nem is nagyon gyakori ott, valószínűleg nem az lesz.

Másrészt azt eddig egyedül a microchip 8 bites C fordítójánál láttam, hogy a const-ot betenné a flash-be, és onnan olvasná. Az AVR-GCC pl. nem így csinálja (https://www.nongnu.org/avr-libc/user-manual/FAQ.html, Why do all my "foo...bar" strings eat up the SRAM?). Tekintve hogy ezek harvard architektúrák, annyira valóban nem is egyszerű.

A nagy esküt nem tenném le rá, el kellene olvasni a doksit, de elég nagy pazarlás lenne, ha a konstans stringeket initben bemásolná RAM-ba flashből. Van benne 2 MiB flash és 512 kiB RAM. Szerintem PIC32 is simán flash-be allokálja a konstans cuccokat, stringeket, struktúrákat, bármit. Nagyon remélem legalább is.

Ebből a szempontból mindegy, hogy azért nem írható, mert flash, vagy azért, mert read only szegmensben van.

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

Egesz konkretan a .rodata-ba teszi, es azt teszi bele kesobb a flash-be. De ugye az ARM mag az nem csak flash-bol tud futtatni hanem RAM-bol is, a RAM-ot meg feltoltheted akar SWD-n keresztul is (es ugy inditod el a rendszert). Na, akkor a .rodata-t is mashova kell linkelni. Szoval ez sem annyira magatol ertetodo teljesen... :]

Tehát szeretnék definiálni egy konstans stringet, vagyis azt akarom, hogy sem a változó által mutatott cím, sem a string tartalma ne változzon. Ha jól értem, a

const char* FIX="fix";

ezt definiálja. Ha az elejéről a const elmarad, akkor a FIX pointer értéke változhat, de maga a szöveg akkor sem.(?)

Rosszul érted. Ez csak azt mondja, hogy a sztring tartalma nem változtatható, de hogy a FIX nevű változód mire mutat, az változhat a program futása során.

Amit akarsz, a következő két módon tudod:

const char c1[] = "x";
const char * const c2 = "y";

Alább meg egy példaprogram, hogy van két nem változtatható sztringed, amire nem változtatható pointerek mutatnak (c1 és c2); és van egy pointer, ami változhat, hogy hová mutat, de csak nem változtatható sztringekre mutathat (v):

#include <iostream>

using namespace std;

const char * const c1 = "x";
const char * const c2 = "y";

int main()
{
    const char * v;
    v = c1;
    cout << v << endl;
    v = c2;
    cout << v << endl;

    return 0;
}

 

A C-t, bár régen belőle kerestem a kenyeremet, ma már a könnyű öntökönlövés nyelvének érzem. Onnan indul, hogy első ránézésre könnyűnek tűnik, közben egy nagyobb projektet nézve borzasztóan egyszerű benne csendben megbúvó módon téves számítási eredményt adó kódot írni. A benne levő nyers, védelmet mellőző pointer trükkök szintén szépek, de szintén a sunyi hibák melegágya. Bár tény, hogy sokan szeretik megülni a bikát is, mert például macsó dolognak tartják, de én már inkább azt vallom, hogy feleslegesen nem célszerű leesni. Legalábbis nem hiányzanak egy-egy nagyobb projektben a low-level hibák általi kockázatok, éjszakába nyúló hibakeresések. Rust esetén az iménti C++ példa ennyi és közben ugyanolyan gyorsan fut:

const C1: &str = "x";
const C2: &str = "y";

fn main() {
    let mut v = C1;
    println!("{v}");

    v = C2;
    println!("{v}");
}

Lehet próbálkozni a kijátszásával.

Szerintem meg a szabadságot jelenti. Gyűlölöm azokat a software-eket, amelyek „kitalálják” helyettem, én mit akarok, persze, mivel szeretem a különutas megoldásokat, sohasem azt akarom, így ezek a segítségek állandóan akadályoznak a munkában.

Nekem ki ne találja egy fordító, hogy szerinte én mire gondolok, mert nem. Nem arra.

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE