Wlasna implementacja sizeof()

0

Witam!
Zwracam sie z prosba o pomoc w sprawie napisania w C++ funkcji zwracajacej rozmiar typu danych zmiennej podanej jako argument. Taki wlasny sizeof(). Nie chodzi mi o jakiegos gotowca czy cos podobnego.

Interesowaloby mnie raczej to, w jaki sposob mam sie do tego zabrac (co wykorzystac itd.) i ogolnie jak by to mialo wygladac. Wczesniej nie zajmowalem sie funkcjami tego typu, raczej tylko algorytmika.

0

Efektywnie najprościej użyć wskaźników - efektem powiększenia wskaźnika na konkretny typ jest przesunięcie go o rozmiar danego typu, z tego zaś wynika, że powiększenie adresu zerowego da sam rozmiar:

#include <cstddef>
// ...

template <typename Type>
size_t fsizeof(const Type &) {
	return (size_t)(((Type *)0) + 1);
}
0

No fajowo, dziala :P
Tylko chcialbym jakies sensowniejsze wyjasnienie do tego, ze wskaznikow za wiele nie umiem...

0

Też bym poprosił o wyjaśnienie.
W szczególności:
((Type *)0)

0

Wskaźnik służy do przechowywania adresu w pamięci, wskazywania na ten adres, ew. leżący tam obiekt. Siłą rzeczy różnice pomiędzy adresami kolejnych obiektów muszą być równe ich rozmiarowi. Więc załóżmy, że mamy pod adresem 0 tablicę obiektów, ten pod indeksem 0 będzie miał adres 0, następny natomiast będzie mieć adres równy rozmiarowi typu, adres kolejnego już 2*sizeof(typ). Używając indeksowania można wcześniejszą funkcję zapisać jaśniej:

template <typename Type>
size_t fsizeof(const Type &) {
        return (size_t)&((Type *)0)[1];
/* 
        lub wręcz:

        Type *wirtualnaTablica = 0;
        return (size_t)&wirtualnaTablica[1];
*/
}

@pcmcymc (niżej), nie, chodzi tylko o różnicę potencjalnych adresów obiektów, nie same obiekty. Wskaźnik jest tylko wartością opisującą adres.

0

Nie musisz utworzyć tych elementów poprzez new?

0

to ja się nieładnie wtrącę w temat i wyjaśnię.

jak wiemy, wskaźnik przechowuje początek adresu, od którego opisana jest pewna dana.

Żeby poznać rozmiar danej w pamięci, możemy sobie stworzyć wskaźnik, który zakłada, że w pewnym miejscu jest ta dana, nie interesuje nas co tam jest, liczy się to, żeby kompilator wierzył, że tam jest coś takiego.

Następnie potraktujmy ten wskaźnik jako nazwę tablicy, tj. jeśli masz np. int tab[10], to jeśli napiszesz w programie tab, to inaczej, jakbyś napisał &tab[0] (& - ampersand to operator pozwalający poznać adres pamięci, w której przechowywana jest zmienna),
jeśli do wskaźnika ((Type *)0) dodamy jeden, tj. przejdziemy na kolejny element w tej naszej pseudo tablicy (należy pamiętać, że to, co robimy, to inkrementowanie wskaźnika, nie jego wartość zmiennej, na która wskazuje).

Kiedy znamy adres początku ciągu bitów opisujących zmienną, która byłaby druga z kolei w tablicy, której początek też znamy (tj. 0), to różnica adresów, na które wskazują te wskaźniki to to, czego szukamy.

0

Juz w miare rozumiem.
Moglby tylko ktos jeszcze wyjasnic roznice pomiedzy wskaznikiem a ampersandem, skoro oba w jakis sposob wskazuja na adres?

0

Ja wiem że to to samo, ale jak już chcemy zerowy wskaźnik, to lepiej użyć NULL a nie 0.

0

@tomciokotar, wskaźnik (jako zmienna) przechowuje adres, operator pobrania adresu (ampersand) zwraca adres tego co stoi po jego prawej stronie.

@Azarien, w sumie fakt. I tak użycie size_t, tak jak i NULL, wymaga włączania włączenia cstddef. Akurat z 'bez-nullowym' kodem pracowałem, leci poprawka.

// edit, albo i nie, akurat tutaj ze względów 'dydaktycznych' użycie zera wprost nie jest złym pomysłem, jasno określa zerowy adres, nie pusty wskaźnik.

0

Ja gdzieś czytałem, że używanie 0 jest dużo lepszym pomysłem niż NULL.

0
pcmcymc napisał(a)

Ja gdzieś czytałem, że używanie 0 jest dużo lepszym pomysłem niż NULL.

Można wiedzieć gdzie? Tak czy inaczej bezsens.

0

Można wiedzieć gdzie? Tak czy inaczej bezsens.

Niekoniecznie.

Po wprowadzeniu do C statycznej kontroli typów (a raczej tylko jej drobnej namiastki, żeby programiści nie musieli za często rzutować :) ustanowiono typ void uniwersalno-wskaźnikowym (wcześniej tą rolę pełnił char*), którego wartość mogła się dowolnie niejawnie konwertować z innym wskaźnikiem (drobna uwaga - konwersje między innymi wskaźnikami, mimo że dopuszczone przez ANSI C, są zwykle obarczane ostrzeżeniami przez kompilator). Wszelcy ideologiczni wykładowcy języka C zaczęli bardzo restrykcyjnie wymagać używania tej "specjalnej stałej NULL", wskutek czego w programach w C jest ona często używana. Niewielu pozostało takich, którzy nie dali się nabrać na ten idiotyzm. Zwłaszcza że ANSI C nie tylko nie zabrania używać 0 zamiast NULL (0 nie musi być rzutowane na void*, jak NULL to ma zdefiniowane na wielu kompilatorach), ale też każdą wartość wskaźnika pozwala niejawnie konwertować na typ int (w C++ niejawnie skonwertuje się najwyżej na bool, natomiast w C konwersja pomiędzy dowolnym typem całkowitym – w tym char – a typem wskaźnikowym odbywa się niejawnie bez żadnych problemów). Jednak to, że void* jest typem uniwersalno wskaźnikowym, pozwoliło zdefiniować NULL jako (void*)0 (w celu umożliwienia sprawdzania poprawności jego użycia) i przypisywać rezultat malloc bez rzutowania. Ta nieszczęśliwa właściwość spowodowała, że NULL w C swoje zadanie spełnia lepiej, niż w C++.

Dlaczego nieszczęśliwa? Dlatego, że stanowi poważną wyrwę w systemie typów języka C. Dla mnie rzeczą niedopuszczalną jest, aby przypisać bez rzutowania wartość typu void* do zmiennej typu Klocek*, czyli w sposób niejawny złamać system typów (bo taka operacja spowoduje zwyczajnie zmianę interpretacji kawałka pamięci). Nie mówię, że takiej operacji nie powinno się wykonywać, ale w C++ wykonuje ją tylko i wyłącznie statyczne rzutowanie. Przyglądając się zresztą jakimkolwiek źródłom w C można jasno zauważyć, że nikt nigdzie nie dokonuje konwersji pomiędzy wskaźnikami bez rzutowania (w GTK wprowadzono nawet specjalne makra, żeby rzutować obiekt z typu "bazowego" na typ "pochodny"). Zabraniają tego takoż wszelkie standardy kodowania. No i oczywiście także kompilatory się czepiają, tylko że nie w sytuacji konwersji z udziałem void* (bo jest to często używane w związku z malloc).

Twórcy C++ nie mogli sobie z założenia pozwolić na żadne "dziury w płocie", uważając je za uzasadnioną niezgodność. Nie ma on zatem ani czegoś takiego, jak typ uniwersalno-wskaźnikowy, ani specjalna stała NULL (a przynajmniej nie ma w niej nic specjalnego). W tym języku przyjęto po prostu, że literał `0' (i - niestety - również dowolne bezstanowe wyrażenie o wyniku 0, np. 2-2!) może być niejawnie konwertowany na dowolny typ wskaźnikowy, tworząc wartość zwaną "pustym wskaźnikiem", ale - UWAGA! - typu tego wskaźnika. Bo - z uwagi na statyczny system typów języka C++ - tylko tak można to pojęcie w nim prawidłowo określić. Dlatego w C++ NULL definiuje się jako 0, gdyż jego wycofanie byłoby nieuzasadnioną niezgodnością z C.

http://www.intercon.pl/~sektor/cbx/advanced/intro.html

A tutaj o tym przeczytałem

W nowym standardzie zauważa się jakby odwrót od stosowania tych słów. To znaczy nadal tak można mówić, ale w programie tych słów się nie używa. Zamiast nich używa się zera.

Może się okazać, że danemu najnowszemu kompilatorowi nazwa NULL jest po prostu nieznana. Wtedy miżesz sobie pomóc syrektywą preprocesora.

#define NULL 0

ale najlepiej pozamieniaj te NULLe ze starego programu - na zera.

Symfonia C++ Standard J. Grębosz - bardzo wiarygodne źródło

0
deus napisał(a)
#include <cstddef>
// ...

template <typename Type>
size_t fsizeof(const Type &) {
	return (size_t)(((Type *)0) + 1);
}

ja opowiadam się jak najbardziej za tym, aby użyć tutaj wskaznika na 0x0, bo to jest właściwie malutka funkcja i jej kod nie musi wykorzystywać zewnętrznych definicji.
Dlaczego? Nie bedę powoływał się na jakiekolwiek publikacje.
Po prostu, zastanówcie się. NULL to zdefiniowana stała, stała, która przyjmuje umowną wartość 0. Ale czy wszędzie to musi być 0? Nie daj Boże, ktoś wymyśli coś innego i zacznie definiować null jako 'N'*2563+'U'*2562+'L'*2561+'L'*2560.
Co nas interesuje w funkcjach i bibliotekach? Mają działać, po prostu. To nie jest problem użytkownika (tj. programisty używającego) danej biblioteki, aby funkcja miała to, czy to. Biblioteka ma działać. Bezbłędnie. Zawsze.. Standardy powstają po to, aby sporne sprawy rozwiązywać i wyznaczać kierunek myślenia, w jakim powinni dążyć osoby o różnych wyobrażeniach co do rozwiązania danego problemu.
Teraz wyobraźcie sobie co stałoby się, jeśli zdefiniowalibyśmy sobie NULL jako 0xDEAD.

#define NULL 0xDEAD
template <typename T >
size_t fsizeof(const T&) {
	return (size_t)(((Type *)NULL) + 1);
}

Wynik działania tej funkcji będzie błędny, a przecież tego bardzo chcemy uniknąć.
Może za kilka lat ktoś wymyśli coś takiego, że NULL to 0xDEAD. Według mnie dyskusja dot. tego, czy należy tutaj użyć NULL, czy 0 może zostać zobrazowana tym, czy należy w tym zadaniu obliczyć najdokładniejszą możliwą postać dziesiętną rozwinięcia liczby pi.

zadanie, oblicz: napisał(a)

1+6674pi*0

Po co robić coś, co można, po •1. zoptymalizować, po •2. zostawić w formie kompatybilnej-w-przód, po •3. nie wykorzystywać zewnętrznych definicji, tylko skorzystać z zaimplementowanych i bardzo lubianych operacji na typach w języku (tj. rzutowanie wskaźniki).

Do tego powiem, że jeśli jesteś naprawdę zwolennikiem definiowania pustych wskaźników jako NULL<font size="1">, tak jak ja</span>, to ten kod sizeof powinien wyglądać tak:

template <typename T >
size_t fsizeof(const T&) {
	return (size_t)( (((Type *)NULL)+1) - ((Type *)NULL) );
}

;p

0

Grębosz bredzi od rzeczy, przecież NULL jest jasno zdefiniowane w C++, zaś standard mówi o tym tyle:

[diff.null] C.2.2.3 Macro NULL
The macro NULL, defined in any of <clocale>, <cstddef>, <cstdio>, <cstdlib>, <cstring>, <ctime>, or <cwchar>, is an implementation-defined C++ null pointer constant in this International Standard (18.1).

Jak nieznane? NULL ma jeden wielki plus - podnosi czytelność pisanego kodu.

@test30, co Ty odstawiasz? Zero jest generalnie gwarantowaną wartością NULL...

test30 napisał(a)

ja opowiadam się jak najbardziej za tym, aby użyć tutaj wskaznika na 0x0, bo to jest właściwie malutka funkcja i jej kod nie musi wykorzystywać zewnętrznych definicji.

Hm, a co z size_t?

0

Stroustrup uzywa 0. Ważniejsze wytłuściłem.

In C++, the definition of NULL is 0, so there is only an aesthetic difference. I prefer to avoid macros, so I use 0. Another problem with NULL is that people sometimes mistakenly believe that it is different from 0 and/or not an integer. In pre-standard code, NULL was/is sometimes defined to something unsuitable and therefore had/has to be avoided. That's less common these days.

http://www2.research.att.com/~bs/bs_faq2.html#null

Myślę, że to koniec dalszych rozmów na temat NULL.

0

No chyba nie, bo sam zaznaczyl, ze to jego preferencje (1), problem z czyjas niewiedza (2) lub problem dotyczacy czasow sprzed standardu (3).

0

Cóż. Właśnie dlatego to zamyka dyskusję. Czy uzywasz NULL czy używasz 0, jeden pies. To tak samo bezsensu się o to spierać, jak o to, czy pisac 0 czy '\0', pomijam wchar_t:)

A co do samego tematu, jeśli kogoś dodatkowo interesują sprawy pack/align zerknijcie na helper strideof<T>()

1 użytkowników online, w tym zalogowanych: 0, gości: 1