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)
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
Mar reg volt, hogy CPPztem, szoval ez egy kicsit a fixme kategoria lesz.
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.
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.
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.
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:
|
V
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?
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.
Így van, fent már írták, nem is reagáltam erre a kérdésre.
dynamic extent = dinamikus bővítésű
Ez nagyon rossz fordítás. Ebben az esetben nincs szó semmilyen bővítésről. Amit fent írtam, azt nagyjából dinamikusan meghatározott méretű nyers adattömbnek lehetne fordítani, de ez sem elég jó.
https://eli.thegreenplace.net/2009/10/21/are-pointers-and-arrays-equiva…
Megnéztem, hasznos.
tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE
Ez autentikus véleménynek tűnik.
> Sol omnibus lucet.
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.
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.
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.
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
> tömb a memóriába, amelynek ismered a címét. Ezt a címet nyilván nem tudod megváltoztatni
Azt gondolom, ez a legfontosabb különbség. Magasabb szintről fogalmazva: a char[] típus az adat tulajdonosa, a char* csak referál valaki másnak az adatára.
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:
tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE
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.
Azért ebben a példában para, ha azt a ptr_place nevű pointert felülírjuk, s nem mentettük el máshová, mert az életben többet nem találjuk meg, hol a fenében is van az a string a memóriában.
tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE
Kontrasztként rozsdásodhatunk picit. Itt egy hasonló Rust példa.
Juj, de nem tetszik nekem ez a rust! Ez valami Pascal és BASIC szerelemgyereke? :D
tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE
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:
Ezzel fel is fedted a C++ egyik rákfenéjét, amit számtalanszor megemlítek. Valami
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.
Már van, gitbe integrálva vagy a build részeként is elég jól működik a clang-tidy, egyedi tesztekkel is bővíthető.
https://clang.llvm.org/extra/clang-tidy/
Két dolog van.
1) Nem beszélek C++-szul
2) Nem szokásom olyan konstrukcióban megadni a stringet, hogy egy szál pointer változó mutat rá, amit felülírva elveszítem azt
:)
tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE
Hát ez érthető aggály, az ember néha öntudatlanul megváltoztatja egy pointert értékét, és kész a baj. Esetleg:
https://yosefk.com/c++fqa/const.html#fqa-18.5
Ez teljesen jogos. :) Nyilván lehet a pointer is const, nem csak a string.
tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE
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
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.
Az nagyon furcsa, ha sikerült ezt írnom egy kódba? :)
Lefordult, holnap kiderül, jó-e.
tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE
Teljesen jo ez, bar nem eroltetnem az ilyen jeloleseket ;) Meg a mar-mar klasszikusnak szamito *p++ meg hasonlokat is inkabb kerulom, ha lehet.
Miért, mit írsz helyette? A *p++ teljesen áttekinthető, pihenteti a szemet. :)
tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE
a=*p; p++ :) de valoban vannak jo alkalmazasok (fifo-zas), ahol en is a *(p++%...) valami format szeretem.
Vagy lehet ringbuffert úgy, hogy a = *p++; if (p - buff >= sizeof(buff)) p = buff;
Ma egész nap effélékkel pusztítottam magam. :)
tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE
... és amikor nagyobban kell gondolkozni, akkor jöhet a thread-safe ringbuffer problémája.
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.
Na, erre szoktam azt mondani, hogy, ha egy (program)rendszerben az időzítéseket (ki-kihez képest-mikor- mit csinál) megoldottad, akkor onnantól kezdve némi túlzással favágás a programozás.
> Sol omnibus lucet.
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.
Akkor hasonló dolgokat csinálunk. Nem akarom túl kibeszélni a munkát, mert nem helyes. Amiről én beszélek, az is egy mérőrendszer belseje. :) További jó munkálkodást neked is! :)
tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE
Egyébként ha már a
Basic és PascalO'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.
Nem oprendszer fölött programozok, csak csupaszon mikrokontrollerre. Elég gyors, de egyetlen magos.
tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE
Egyetlen mag az a könnyebbik eset. Itt csak az interruptot kell tiltani a kritikus műveletek idejére, ha IRQ-ból is van kezelve.
A 3.0-s gcc (~20 éves!) is képes kiszedni a redundáns/felesleges ellenőrzéseket, szóval eléggé elvárható asszem, hogy a Rust megtegye ezt az optimalizálást. Én inkább olyan optimalizálásra lennék kíváncsi, amit a Rust megcsinál, viszont a gcc/clang nem.
Nézegettem a fentebb megemlített kifejezést: https://rust.godbolt.org/z/ocv19GhnW
Megpróbáltam átírni C-re ugyanezt a funkciót.
Hosszabb lett az assembly kód. https://c.godbolt.org/z/vzdYc93Yx
Valakinek valami ötlete?
A slice-t ne pointerekkel add át/vissza, hanem simán. Nem is értem a logikát, miért így csinálod, rendkívül csúnya ez a megoldás így (globális objectet csinálni csak azért, hogy vissza lehessen pointerként adni). A Rustos megoldás se pointerekkel működik.
Azzal sikerült rövidebb assembly kódot csinálnod? Légyszi mutasd meg, had tanuljak.
https://godbolt.org/z/vcaKd6Po1
Köszi, ügyes kód, tetszik.
Viszont ez C++ nyelv. Tudnál esetleg C-ben is valami ügyes megoldást?
Nem tudok ilyesmi "szép" megoldásról C-ben, mivel nem lehet fix méretű tömböt általánosan átadni úgy, hogy a méretinformáció is megmaradjon. De persze aztán lehet, hogy van valami megoldás, C-ben nem vagyok annyira otthon (pláne C11 és felette dolgokból).
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:
... 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.