Zárt tömb, nyílt tömb C++-ban [megoldva] + új kérdés

Fórumok

Azt szeretném kérdezni, hogy nem lehet, hogy mégis van
különbség az alábbi belépési pontok között?

int main(int argc, char** argv)

int main (int argc, char *argv[])

Külföldön azt mondták nekem, hogy nem ekvivalensek,
mégpedig a második ún. zárt tömb-re hivatkozik, míg
az első nyílt tömbre.

Avagy a ** nyílt tömbre mutató pointer, míg a
* tomb[] zárt tömbre mutató pointer.

 

Akkor is ha igaz, csak szemantikailag mondjuk, de szerintem

a C++ szintaktikailag nem ellenőrzi. Tényleg létezik így valami, hogy

zárt tömb vagy nyílt tömb?

A másik dolog az, hogy hogy lehet megcsinálni, hogy tetszőlegesen

hosszú argumentum legyen? Vagy nincs argc, hanem teljesen dinamikus?

Egyik munkahelyen már volt és valami ...-t kellett használni, de ott nem

gcc volt a fordító, hanem valami ipari fordító volt, talán IAR?

 

Közben megoldva:

// zárt tömb

    char a = array_place[7];

0041137E  mov  al,byte ptr [_array_place+7 (417007h)]
00411383  mov  byte ptr [a],al

// nyílt tömb
    char b = ptr_place[7];

00411386  mov  eax,dword ptr [_ptr_place (417064h)]
0041138B  mov  cl,byte ptr [eax+7]
0041138E  mov  byte ptr [b],cl

 

Szóval arról van szó, hogy mégis így volt: zárt tömbnél csak byte a pointer,

míg nyíltnál dword, mivel az a lehető leghosszabb is lehet.

 

Még kérdés lenne az, hogy van valami amit tilos programozáskor zárt tömbökkel

művelni, mert azt mindig nyílt tömbökkel kell. Ki az aki tudja?

Hozzászólások

https://infoc.eet.bme.hu/advent/?v=12

bár sosem C-ztem, vagy C++-ztam többet annál, mint ami egyetemen volt, szóval lehet fasság, de a második kérdésedre szerintem ez a prog1 lecke a válasz

(az elsőt meg nem is értem, de nem vagyok cpp ember)

Szerkesztve: 2021. 04. 19., h – 16:34

Most lebukok a tudatlanságommal és a lustaságommal, ugyanis ki lehetne ezeket deríteni egy nyúlfarknyi programmal kiíratva a pointereket. Néha elmélázom azon, hogyan ábrázolják ezt a memóriában. Ha teszem azt, ezek különböző hosszúságú zero terminated stringek, akkor el lehet képzelni olyasmit, hogy egymás után vannak ezek a stringek, ezen felül van egy tömb, amely a stringek nulladik karakterére mutató pointereket tartalmazza, s ezen pointertömb nulladik elemének címe, ahonnan az élet indul. Szerintem ez egyébként így kerül ábrázolásra. Az n. elemre hivatkozással a pointertömbből kivesszük az n. pointert - ugye, a pointer mérete fix, így ez nem probléma -, majd ez a pointer már címzi a string nulladik karakterét.

Ugyanakkor el tudok képzelni egy merev, statikus ábrázolást - táblázatoknál lehet csinálni, de ott nem, ahol nagyon rövid és nagyon hosszú is lehet az adott string -, hogy fix hosszúságú helyek, pl. 32 byte-ok vannak lefoglalva. Egy-egy ilyen 32 byte-os blokkban lehet egy legfeljebb 31 byte hosszú string, plusz a termináló '\0', s az n. elemre lehet úgy hivatkozni, hogy báziscím + 32 * n, ahol most a 32 a példában adott egy string számára termináló nullával együtt fenntartott hely. Ebben a statikus esetben nincs külön táblázat, amely tartalmazná a pointereket, hiszen a pointer értéke számolható a tömbindexből a fix elemméret miatt.

És igen, a változó mennyiségű függvényparamétert a ...-tal illetve ezekkel a va_ kezdetű csodákkal lehet megvalósítani. Lehetőleg kerülni kell, de van, hogy jól jön.

Egyébként mi a fene az a zárt meg nyílt tömb? Sohasem tanultam C-ben programozni, de mostanában épp ez a dolgom a munkahelyemen. :) Korábban assembly-ben sokat programoztam, így jellemzően tudom, mire kell figyelni, pl. mikor fontos az, hogy a művelet atomikus legyen, vagy milyen alignment-et írjon elő az ember egy bufferhez. Inkább az a hátránya, hogy kevésbé pontos a szóhasználatom.

Szerk.: gcc természetesen minden nehézség nélkül lefordítja majd a ...-tal deklarált függvényedet.

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

Szerkesztve: 2021. 04. 19., h – 16:52

Mar reg volt, hogy CPPztem, szoval ez egy kicsit a fixme kategoria lesz.

Azt szeretném kérdezni, hogy nem lehet, hogy mégis van
különbség az alábbi belépési pontok között?

int main(int argc, char** argv)

int main (int argc, char *argv[])

A ketto tenyleg nem ugyan az. A CPP-ben (es C-ben is) a pointer kepzo, modosito nem a tipushoz tartozik, hanem a valtozo nevehez. Tehat pl. char* x,y; az egyenlo azzal, hogy char *x,y; azaz az "x" az pointer, a "y" valtozo pedig nem.

Ezaltal a char **argv az egy pointerre mutato pointer, a char *argv[] pedig egy pointerekbol allo tomb. Van ezentul egy masik szabaly is, miszerint egy tomb valtozo az automatikusan konvertalodik a &tomb[0]-ra, azaz az elso elemre mutato pointerre, de ez nem azt jelenti, hogy a tomb az egy pointer, mivel a tipusa mas, csupan van egy automatikus konverzio, ha csak annyit irsz, hogy "tomb". Pl. int tomb[20]; akkor, ha annyit irsz, hogy "tomb" akkor azt automatikusan konvertalja azza, hogy &tomb[0]. Az argv-s valtozat pedig konnyebben kezelheto. Nezz ra erre a kis programra, amit itt irtam.

Külföldön azt mondták nekem, hogy nem ekvivalensek,
mégpedig a második ún. zárt tömb-re hivatkozik, míg
az első nyílt tömbre.

Mondjak nyugodtan, meg eletemben nem lattam ezt a kifejezest, egy ** jelenthet tombot is, meg mast is, mig egy *[] kifejezetten utal arra, hogy van ott egy tomb. Szerintem ez ilyen farokveres szintu dolog lehet, ahol valaki nagyon meg akarta mutatni, hogy tud valamit, mert a C++ nyelvben ilyen nincs.

A másik dolog az, hogy hogy lehet megcsinálni, hogy tetszőlegesen

hosszú argumentum legyen? Vagy nincs argc, hanem teljesen dinamikus?

Az argc-t az operacios rendszer tolti ki, attol fuggoen, hogy hany parametert adsz meg, tehat tetszoleges hosszu lehet. Ha viszont fuggvenyeknel szeretnel tetszoleges parametert atadni, akkor azt ugyhivjak, hogy varargs, arra keress ra. Mar oszinten szolva nem emlekszem fejbol pontosan, hogy kell csinalni, de van ilyen es a printf es tarsai is igy mukodnek. Ezzel lenyegeben egy listakent tudod elerni a fuggveny maradek parametereit. Itt erdemes megnezni a See Also resszel egyutt.

Szerk. Ha csak C-s tipusokrol van szo, akkor ez a kis oldal nagyon hasznos tud lenni.

A 2 main verzió teljesen ugyanaz, nincs semmi különbség köztük. A tömb paraméter pointernek minősül ebben az esetben. Nyilván más kontextusban a tömb és a pointer másképp viselkedik, de itt ugyanazt jelentik.

Olyanról pedig én még nem hallottam, hogy nyitott/zárt tömb. Lehet értettek alatta valamit, bár ha vki szerint ez a 2 main mást jelent, nem biztos, hogy érdemes komolyan venni, amit mond. Azt bírom esetleg elképzelni, hogy mivel a main fv. pontos deklarációja implementation-defined, ezért lehet, hogy van valami fordító, aminek van valami speciális szabálya csak erre az egy esetre.

Szerkesztve: 2021. 04. 19., h – 18:38

A C++17 szabvány előtti verzióknál nincs lényeges különbség a ket tömb között. Az új szabvány szerint a zárt tömb már tartalmaz méret információt is. Így lehet őket például iterálni, kezdő értékként is meg lehet adni méret információk nélkül. A C++20 annyival tovább megy, hogy sablonoknál is felhasználható ez a méret információ.

Itt van néhány példa: https://en.cppreference.com/w/cpp/container/span

Bocs, "zárt tömb"-ről soha nem hallottam c++ programozó létemre. Ez valami teljesen más angol kifejezés lehet félrefordítva.

A span-nak meg alapból a tömbökhöz semmi köze, az csak egy kezdő pointer + egy elemszám, egy papírvékony burkoló ekörül a két érték körül.

A két main ugyanaz, mindkettőnél N darab pointer van elhelyezve a memóriában folytonosan, az első pointer címe az argv:

argv
|
V
       
argv[0]   argv[1]   argv[2]   argv[3] ... argv[argc-2]   argv[argc-1]  
char*   char*   char*   ...   char*  

Bocs, "zárt tömb"-ről soha nem hallottam c++ programozó létemre. Ez valami teljesen más angol kifejezés lehet félrefordítva.

Erre vonatkozott az egyik kérdés, azért használtam ezt a kifejezést. A "POD array with dynamic extent" magyar megfelelője nem tudom mi lehet, hogy mindenki ugyanazt értse alatta. Neked van jobb ötleted?

A span-nak meg alapból a tömbökhöz semmi köze

Azt írtam, hogy ott (is) vannak példák amik segíthetnek megérteni mi az a "dynamic extent" (azaz a különbség a két tömb típus között). Annyiban van köze hozzá, hogy az általad is említett elemszámot ebben az esetben a fordító fogja megadni.

A két main ugyanaz

Így van, fent már írták, nem is reagáltam erre a kérdésre.

Olyan terminológiát használsz, ami a C++-ban ismeretlen, szóval először a fogalmakat kellene tisztába tenni. A nyílt/zárt tömb kifejezésre 0 találat van magyarul C++ témakörben, és angolul is csak Delphihez van találat a google-ban az "open array"-ra.

Szóval arról van szó, hogy mégis így volt: zárt tömbnél csak byte a pointer,

Az elsőben nincs pointer változó sehol (szóval a megállapításodnak nem nagyon van értelme), hanem a program adatszegmenséből közvetlenül van kiolvasva egy byte. Aminek ugye ott a címe (417007h), ami egy 32 bites érték. A másik esetben pedig egy pointer olvasás van először, és az alapján van kiolvasva az adott byte.

Ha tényleg ez a megoldás, akkor semmi köze nem volt a kérdésedhez. Az első egy char adattömb egy elemére mutat (pointer + char offset), a másik meg egy pointertömb által indexelt elem. Azaz az array_place char[] (vagy char*) típusú, a ptr_place meg char*[] (vagy char**). A belépési ponthoz semmi köze. Hogy mindez mitől nyílt vagy zárt, nem világos nekem sem. A zárt tömbnél a tömb konstansra gondoltam, hibásan.

Szóval arról van szó, hogy mégis így volt: zárt tömbnél csak byte a pointer, míg nyíltnál dword, mivel az a lehető leghosszabb is lehet.

Bocs, össze-vissza dobálózol kifejezésekkel, amit szerintem egyáltalán nem értesz, rossz következtetéseket vonsz le.

Nem jól olvastad az assemblyt. A pointer sose lesz byte. A címről kivett érték volt a byte.
A pointeres példánál természetesen plusz egy indirekcióra szükség van, mert a pointer változóban levő címet először be kell tölteni regiszterbe.
A tömbös példánál ez a cím egy fordítási (esetleg betöltési) időben ismert konstans, ezért a gépi kódba bele tudta tenni azonnal a compiler.

Még kérdés lenne az, hogy van valami amit tilos programozáskor zárt tömbökkel művelni, mert azt mindig nyílt tömbökkel kell. Ki az aki tudja?

Szerintem rajtad kívül itt senki sem hallott ilyen állatfajtáról.

Ha jól értem, amit zártnak nevezel, ott le van téve egy karakter tömb a memóriába, amelynek ismered a címét. Ezt a címet nyilván nem tudod megváltoztatni, erre tudsz hivatkozni. Amit meg nyíltnak nevezel, ott is van egy karakter tömböd, azaz egy stringed a memóriában, de van egy pointered is valahol, ami rámutat a stringed elejére. Mivel ez egy változó, így tudod módosítani az értékét. Nem nagyon látom a különbséget aközött, mintha kevésbé tömören fogalmaznál, s létrehoznál egy char* pointert, amelynek a tömbödet, tehát annak címét adnád értékül.

Vagy mindent félreértettem. :)

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

Amit a megoldáshoz írtál, ott nem látszik, miért fordul mássá a két kód, ami az általad idézett példában egyformának tűnik. Fontos lenne a körülmények tálalása is. Ezért:

char array_place[100] = "don't panic";
char* ptr_place = "don't panic";

int main()
{
    char a = array_place[7];
    char b = ptr_place[7];

    return 0;
}

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

char array_place[100] = "don't panic";
char* ptr_place = "don't panic";

A különbség egyébként:
    array_place adatai data szegmensben fog helyet kapni
    ptr_place  adatai readonly szegmensben vannak (viszont a pointere átdefiniálható pl. az array_place helyére is, mivel nem const)

Próbáld meg olvasás helyett írni a 7. pozícióját. A .rodata szegmensben az írási hozzáférés tiltott, segfault-ot fog eredményezni.

Egy C nyelvű kódnál tényleg para, de a C++ nyelvben már nem érvényes ez az értékadás (ptr_place). Valamiért ebben a topikban a C sting konstans pointeres tömbfeldolgozása a menő. Ha már a címben meg lett említve a C++, akkor ezért legyen legalább egy példa, ami nem a K&R módszert követi:

struct Data {
    static inline std::string const string_place = "don't panic";
};

Ezzel fel is fedted a C++ egyik rákfenéjét, amit számtalanszor megemlítek. Valami

#pragma strictmode

kellene a C++ -ba is, és ahol ez ki van adva, onnantól a forrásfájl végéig nem engedné keverni a C++-2x nyelvezetet C-s alágányolással.
Találkoztam én is nem egy C++ kóddal, ahol a kód egy része C++, a többi meg alágányolás.

Szóval arról van szó, hogy mégis így volt: zárt tömbnél csak byte a pointer, míg nyíltnál dword, mivel az a lehető leghosszabb is lehet.

Hát, izé... Az a byte ptr olyasmi, mintha (char *) cast-olás lenne. Ahol dword címre hivatkozik, ott még csak a pointert tölti be, de a pointer az széles, dword. Utána már az a pointer is csak byte szélességű adatot címez.

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

Még kérdés lenne az, hogy van valami amit tilos programozáskor zárt tömbökkel

művelni, mert azt mindig nyílt tömbökkel kell. Ki az aki tudja?

Nem tilos a C++ nyelvben a C stílusú pointer zsonglőrködés, csak kerülendő. Tömbölnél a POD + STL a megfelelő irány általában.

Itt részletesen megtalálod mit miért nem ajánlott a C++ nyelvben csinálni:

https://www.perforce.com/resources/qac/high-integrity-cpp-coding-standa…

https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines

lehet, hogy te úgy látod, hogy megoldva, de az egyik az char-ból álló tömb, a másik pedig pointerekből álló tömb... mégis, mit olvasson ki, ha nem char-t az elsőből és mit olvasson ki, ha nem pointert a másodikból?

 

amúgy a int main(int argc, char** argv) és a int main (int argc, char *argv[]) teljesen ugyanaz és teljesen ugyanazt a kódot generálja. gcc 10.2-vel néztem.

Szerkesztve: 2021. 04. 22., cs – 22:05

Az nagyon furcsa, ha sikerült ezt írnom egy kódba? :)

c = *--*p;

Lefordult, holnap kiderül, jó-e.

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

Ne is mondd, le kellett tiltanom rövid időre annak a perifériának az IT kérését, amelyik a túloldalon olvassa a FIFO-t. Mert mi a fenét csinálok, ha az író pointerem épp eggyel mutat túl a buffer végén, írnám át az elejére, közben becsap az IT, s nem veszi észre, hogy az olvasó pointer utolérte az írót. Illetve lehet mindenféle ideiglenes változókkal operálni, meg modulót képezni, maszkolni, ha kettő egész kitevőjű hatványa a buffer hossza, stb. :) Megoldás mindig van, csak a kellő távolságból nézve nagyon egyszerű feladatok sem mindig azok, ha közelebbről piszkálja az ember.

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

Többmagos rendszeren a több szál még bonyolultabb. Itt valami 'lock cmpxchg' (i486-tól) jellegű atomi utasításpár kell és vele szemaforozni vagy esetünkben akár értéket cserélni. Nélküle macerásan lehet megoldani több magon eloszló párhuzamos szálaknál.
Lásd: https://www.felixcloutier.com/x86/cmpxchg
Nem véletlen, hogy konkrétan az i386-ot kivették a Linux által támogatott architektúrákból, csak az i486 felettieket támogatják.
Ott még nem volt cmpxchg, enélkül viszont bonyolult volt úgy körülírni, hogy a multi core-ral jól működjön a szálak közötti szinkronozás.

Nagyon kellemetlen, misztikus hibákat, ritkán előforduló furcsaságokat okozhat, ha valaki nem figyel oda arra, mi atomikus, és mi nem, csak úgy tekinti, mintha az lenne. Különösen szép ez, ha megörökölsz egy kódot, majd kiderül, a kolléga egy változó használatával kapcsolatban bizonyos szabályokat tart be, amit nem dokumentált, te nem tudsz erről, aztán megborul az egész. Velem volt ilyen. Egy változóba bele lehetett írni egy küldendő adatmennyiséget, ha az adat elment, a változó visszaállt 0-ba. Én meg azt csináltam, hogy ha hibára futottam, nem akartam, hogy elmenjen az adat, visszatöröltem a változót 0-ba, viszont az adat küldésének állapotautomatája tartott már valahol, de nem a végén, így szépen belefagyott az adott állapotba. Jó volt debugolni ezt. :-/

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

Én egyszer egy duplapufferelt DMA kódot irtam motorolára. 2 DMA-t kellett szinkronban járatni, amíg az egyiket felhúztad, a másik dolgozott. Nem volt egyforma az órajelük, egy kicsit másképp ketyegtek! Nem szabadott adattúlfutásba menni, mert a legfelső adatbitben volt egy szinkronbit, ha az elvész, akkor borul minden. Nagyon vidám feladat volt összeszinkronizálni a dolgot. Úgy írod a programot, hogy közben a 2 csatornás szkóp föl van csippentve a DMA lábra!

 

Na jó, régi szép idők... Most is egyébként egy mérőrendszer dolgozom, itt is van mit összeszinkronizálni. Jönnek a triggerek mindenhonnan és kerülgetniük kell egymást.

 

Üdv és jó munkát!

> Sol omnibus lucet.

Egyébként ha már a Basic és Pascal O'Caml és Haskell szerelemgyermekről beszélünk, ott külön eszköztár van erre.
  - ha nem kell thread-eken átívelni:  https://doc.rust-lang.org/std/collections/struct.VecDeque.html
  - ha thread-ek között kell: https://crates.io/crates/ringbuf    (spawn indít új thread-et)
Csak hogy ne kelljen gondolkozni és ne a saját fejed után vigyél be itt napokig debugolandó hibát.

Megjegyzem, a Rust fránya jószág, érdekesen optimalizál. Nézd meg az assembly kódját ennek az egyszerű példának: https://rust.godbolt.org/z/ocv19GhnW
Futásidejű ellenőrzést alkalmaz a függvényben, de ha rájön hogy ahonnan hívták, ott tuti nem áll fenn, akkor képes fordításidőben validálni és kihagyni a lefordítás során a futásidejű ellenőrzés részt.

Szerintem csekély haszonnal, de működő dolog: mivel Rustban kötelezően tudjuk, hogy melyik adat csak olvasható, ezért némi adatfolyam-analízis után képes műveleteket kihagyni, amiről rájön, hogy nem oszt - nem szoroz.

Ezért nincs "légyszi ez most legyen mutable" kulcsszó, ráadásul a megoszthatóságot korlátozó szabály erősen motivál is arra, hogy ami nem mut, arra csak úgy hátha kell alapon nem írom oda, hogy mut. :)  az alapértelmezés is a konstans adat.

Sőt, ha saját unsafe trükkel akarok írhatóságot ráerőltetni, az undefined behavior.  UnsafeCell-t kell használni, az nyelvi elem, és kikapcsolja ezt a fajta optimálást.  A Cell és RefCell könyvtári cucc is arra épül.

> Még kérdés lenne az, hogy van valami amit tilos programozáskor zárt tömbökkel művelni,

Igen, akkor sokadikként már a terminológián nem rugózva, :) egy nyers példa:

char tomb1[] = "abcd";
char* tomb2 = tomb1;
// char tomb3[] = tomb1; // hiba

... meg aztán elég fontos különbség még, hogy a sizeof( tomb1 ) az adat méretét adja, a sizeof( tomb2 ) pedig a mutatóét.