Próbuję uzyskać lepsze zrozumienie standardu C. W szczególności interesuje mnie, jak arytmetyka wskaźnikowa może pracować w realizacji dla niezwykłej architektury maszynowej.

Przypuśćmy, że mam procesor z 64-bitowym szerokim rejestrami, które są podłączone do pamięci RAM, gdzie każdy adres odpowiada komórek 4 bitów szerokości. Wdrożenie dla C dla tego urządzenia definiuje Char_BIT, aby był równy 8. Załóżmy, że kompiluję i wykonuję następujące wiersze kodu:

char *pointer = 0;
pointer = pointer + 1;

Po wykonaniu, wskaźnik jest równy 1. Daje to wrażenie, że w ogólnych danych typu Rodzaj odpowiada najmniejszej odpowiedniej jednostce pamięci na maszynie.

Załóżmy teraz, że mam procesor z 12-bitowymi szerokim rejestrami, które są podłączone do pamięci RAM, gdzie każdy adres odpowiada komórce 4 bitów szerokości. Wdrożenie C dla tej maszyny definiuje Char_BIT, aby był równy 12. Załóżmy, że te same linie kodu są skompilowane i wykonywane dla tego urządzenia. Czy wskaźnik byłby równy 3?

Bardziej ogólnie, gdy zwiększając wskaźnik do znaku, jest adresem równym char_bit podzielonym przez szerokość komórek pamięci na maszynie?

-2
Dschumanji 4 czerwiec 2018, 22:10

6 odpowiedzi

Najlepsza odpowiedź

Czy wskaźnik byłby równy 3?

Cóż, standard nie mówi, jak wrzeczenie są realizowane. Standard mówi, co ma się wydarzyć, gdy używasz wskaźnika w określony sposób, ale nie jaka jest wartość wskaźnika.

Wszystko, co wiemy, jest to, że dodanie 1 do wskaźnika Char, uczyni punkt wskaźnika na następnym obiekcie Char - gdziekolwiek jest. Ale nic o wartości wskazującej.

Więc kiedy to mówisz

pointer = pointer + 1;

Sprawi, że wskaźnik jest równy 1, jest źle. Standard nic o tym nie mówi.

W większości systemów A char ma 8 bitów i wskaźniki są (wirtualne) adresy pamięci odnoszące się do 8-bitowej luciacji pamięci. Na takich systemach zwiększanie wskaźnika Char zwiększy wartość wskaźnika (aka Adres pamięci) o 1. Jednak na - nietypowe architektury - nie ma sposobu, aby powiedzieć.

Ale jeśli masz system, w którym każdy adres pamięci odnosi się 4 bitów, a znak to 12 bitów, wydaje się dobre, że ++pointer zwiększy wskaźnik przez trzy.

3
4386427 5 czerwiec 2018, 18:33

Wskaźniki są zwiększane minimum szerokością o szerokości DataType "wskazują", ale nie gwarantują zwiększania tego rozmiaru dokładnie.

W celach wyrównawczych pamięci istnieje wiele razy, w których wskaźnik może zwiększyć do następnego wyrównania słowa pamięci obok minimalnej szerokości.

Tak więc, w ogóle, nie można zakładać tego wskaźnika, aby był równy 3. To bardzo dobrze może być 3, 4 lub jakaś większa liczba.

Oto przykład.

struct char_three {
   char a;
   char b;
   char c;
};

struct char_three* my_pointer = 0;
my_pointer++;

/* I'd be shocked if my_pointer was now 3 */

Wyrównanie pamięci jest specyficzne dla maszyn. Nie można o tym uogólnić, z wyjątkiem tego, że większość maszyn zdefiniuje słowo jako pierwszy adres, który można wyrównać do pobierania pamięci w autobusie. Niektóre maszyny mogą określić adresy, które nie wyrównują się do pobierania autobusów. W takim przypadku wybór dwóch bajtów, które obejmują wyrównanie może spowodować załadowanie dwóch słów.

Większość systemów nie przyjmuje słów ładunków na granicach nierównych bez narzekania. Oznacza to, że stosuje się odrobinę montażu płytki kotła, aby przetłumaczyć pobieranie Pobierającego do branki słów, jeśli pożądane jest maksymalna gęstość.

Większość kompilatorów preferuje prędkość do maksymalnej gęstości danych, więc wyrównują swoje strukturyzowane dane, aby skorzystać z granic słów, unikając dodatkowych obliczeń. Oznacza to, że w wielu przypadkach dane, które nie są starannie wyrównane, mogą zawierać "otwory" bajtów, które nie są używane.

Jeśli chcesz omówi wyrównanie (w konsekwencji) wyściółki.

0
Edwin Buck 4 czerwiec 2018, 19:39

char *pointer = 0;
Po wykonaniu wskaźnik jest równy 1

Niekoniecznie. Ten wyjątkowy przypadek daje wskaźnik zerowy, ponieważ 0 jest stałą wskaźnika zerowego. Ściśle mówiąc, taki wskaźnik nie powinien wskazywać na prawidłowy obiekt. Jeśli spojrzysz na rzeczywisty adres przechowywany w wskaźniku, może to być wszystko.

Na bok na bok, język C spodziewa się, że zrobisz arytmetykę wskaźnika przez pierwsze wskazanie w tablicy. Lub w przypadku char, możesz również wskazać na kawałek danych ogólnych, takich jak struktura. Wszystko inne, jak twój przykład, jest niezdefiniowany zachowanie.

Wdrożenie C dla tego urządzenia definiuje Char_BIT, aby był równy 12

Standard C definiuje char, aby był równy bajtowi, więc twój przykład jest nieco dziwny i sprzeczny. Arytmetyka wskaźnika zawsze zwiększa wskaźnik, aby wskazał na następny obiekt w tablicy. Standard nie mówi w ogóle o reprezentacji adresów, ale twój fikcyjny przykład, który rozsądnie zwiększy adres przez 12 bitów, ponieważ jest to rozmiar char.

Komputery fikcyjne są dość bez znaczenia, aby omówić nawet z punktu widzenia uczenia się. Zamiast tego radzę skupić się na komputerach w prawdziwym świecie.

0
Lundin 4 czerwiec 2018, 19:48

Po zwiększeniu wskaźnika do znaku, jest adresem równym char_bit podzielonym przez szerokość komórek pamięci na maszynie?

Na "konwencjonalnej" maszynie - rzeczywiście na ogromnej większości maszyn, w których C uruchamiają - CHAR_BIT Simply jest szerokość komórki pamięci na maszynie, więc odpowiedź na pytanie jest opętoszy "tak" (od CHAR_BIT / CHAR_BIT wynosi 1.).

Maszyna o komórkach pamięci mniejsza niż CHAR_BIT byłaby bardzo, bardzo dziwna - prawdopodobnie niezgodna z definicją C.

Definicja C mówi, że:

  • sizeof(char) jest dokładnie 1.

  • CHAR_BIT, liczba bitów w {x1}} jest co najmniej 8., o ile C jest zaniepokojony, bajt może nie być mniejszy niż 8 bitów. (Może być większy, a to jest niespodzianka dla wielu ludzi, ale nie dotyczy nas tutaj.)

  • Istnieje silna sugestia (jeśli nie jest to wyraźny wymóg), że char (lub "bajt") jest "minimalna jednostka adresowalna" lub niektóre.

Więc dla maszyny, która może rozwiązać 4 bitów na raz, musielibyśmy wybrać nienaturalne wartości dla sizeof(char) i CHAR_BIT (co w przeciwnym razie byłoby prawdopodobnie chcieli być 2 i {{x3} }, odpowiednio), a my musielibyśmy zignorować sugestię typu char jest minimalną jednostką adresowaną maszyny.

C nie mają wewnętrznej reprezentacji (wzór bitowy) wskaźnika. Najbliższy program Portable C może zrobić wszystko z wewnętrzną reprezentacją wartości wskaźnika, jest wydrukowanie go za pomocą %p - i jest wyraźnie zdefiniowany jako zdefiniowany implementację.

Myślę więc, że jedynym sposobem na wdrożenie C na maszynie "4 bit" obejmowałby kod

char a[10];
char *p = a;
p++;

Generuj instrukcje, które faktycznie zwiększają adres za 2.

Byłoby wtedy interesujące pytanie, czy %p powinno wydrukować rzeczywistą, surową wartość wskaźnikową lub wartość podzieloną przez 2.

Byłoby to również wiele zabawy, aby oglądać, aby oglądać kolejne fajerwerki jako zbyt sprytni programiści na takiej maszynie używane techniki knarzęs, aby uzyskać ręce na wewnętrznej wartości wskaźników, aby mogły je zwiększyć przez w rzeczywistości 1 - nie 2, że "właściwe" dodatki 1 zawsze wygenerowałyby - takie, że mogłyby zadziwiać swoich przyjaciół, uzyskując nieparzystą Nybble bajtu, lub zatknij regularnie, więc pytając pytania o to. "Właśnie zwiększam wskaźnik Char przez 1. Dlaczego %p pokazuje wartość 2 większą?"

0
Steve Summit 5 czerwiec 2018, 00:11

Wydaje się, że zamieszanie w tym pytaniu pochodzi z faktu, że słowo "bajt" w normie C nie ma typowej definicji (która ma 8 bitów). W szczególności słowo "bajt" w normę C oznacza zbiór bitów, gdzie liczba bitów jest określona przez stałą implementację CHAR_BITS. Ponadto, "bajt" zdefiniowany przez standard C jest najmniejszym obiektem adresowalnym , że program C może uzyskać dostęp.

Pozostawia to otwarte pytanie, czy istnieje korespondencja od jednej do jednej do jednej między definicją C "Advice", a definicję "adresowalnego" sprzętu. Innymi słowy, czy możliwe jest, że sprzęt może rozwiązać obiekty, które są mniejsze niż "bajt"? Jeśli (jak w OP) A "Bajt" zajmuje 3 adresy, to oznacza, że dostęp "Bajt" ma ograniczenie wyrównania. Co znaczy, że 3 i 6 są ważne "bajt" adresy, ale 4 i 5 nie są. Jest to zabronione przez sekcję 6.2.8, co omawia wyrównanie obiektów.

Co oznacza, że architektura zaproponowana przez OP jest , a nie wspierana przez specyfikację C. W szczególności, wdrożenie może nie mieć wskaźników wskazujących na 4-bitowe obiekty, gdy CHAR_BIT jest równa 12.


Oto odpowiednie sekcje ze standardu C:

§3.6 Definicja "bajtu" stosowanego w normie

[Bajt to] Odpowiednia jednostka przechowywania danych wystarczająco duża, aby przytrzymać dowolny członek podstawowego zestawu znaków środowiska wykonania.

Uwaga 1 Możliwe jest wyrażenie adresu każdego indywidualnego bajtu obiektu jednoznacznie.

Uwaga 2 Bajt składa się z ciągłej sekwencji bitów, której liczba jest zdefiniowana implementacja. Najmniej znaczący bit nazywa się kawałkiem niskiego zamówienia; Najważniejszy bit nazywa się kawałkiem wysokiejrzędności.

§5.2.4.2.1 opisuje Char_bit jako

Liczba bitów dla najmniejszego obiektu, który nie jest polem bitowym (bajt)

§6.2.6.1 Ogranicza wszystkie obiekty, które są większe niż char, aby być wielokrotnością bitów char_bit:

[...] Z wyjątkiem pól bitowych, obiekty składają się z ciągłych sekwencji jednego lub więcej bajtów, liczby, kolejności i kodowania, których są wyraźnie określone lub wdrażane.

[...] Wartości przechowywane w obiektach nie-bitowych dowolnego innego typu obiektu składają się z bity n × Char_bit, gdzie N jest wielkością obiektu tego typu, w bajtach.

§6.2.8 ogranicza dostosowanie obiektów

Pełne typy obiektów mają wymagania wyrównania, które mają miejsce Ograniczenia na adresach, w których obiekty tego typu mogą być asygnowany. Wyrównanie jest zdefiniowaną implementacją wartości całkowitą reprezentujący liczbę bajtów między kolejnymi adresami , w którym Dany obiekt może zostać przydzielony.

Ważne ustawienia obejmują tylko te wartości zwracane przez _alignof Wyrażenie dla podstawowych typów, a dodatkowe Zestaw zdefiniowanych implementacji wartości, które mogą być puste. każdy Ważna wartość wyrównania powinna być nienagatywna integralna moc dwóch .

§6.5.3.2 Określa sizeof char, a stąd "bajt"

Gdy sizeof jest stosowany do operandu, który ma typ typu, niepodpisany char lub podpisany char, (lub ich wykwalifikowaną wersję) wynikiem jest 1.

0
user3386109 5 czerwiec 2018, 01:42

Poniższy fragment kodu demonstruje niezmiennik arytmetyki C-ismetyczny - bez względu na to, co jest CHAR_BIT, bez względu na to, co jest najmniejsze adresowalna jednostka, i bez względu na to, co rzeczywista reprezentacja wskaźników jest,

#include <assert.h>
int main(void)
{
    T x[2]; // for any object type T whatsoever
    assert(&x[1] - &x[0] == 1); // must be true
}

A od sizeof(char) == 1 z definicji oznacza to również, że to

#include <assert.h>
int main(void)
{
    T x[2]; // again for any object type T whatsoever
    char *p = (char *)&x[0];
    char *q = (char *)&x[1];
    assert(q - p == sizeof(T)); // must be true
}

Jeśli jednak konwertujesz na liczby całkowite przed wykonaniem odejmowania, niezmienne odparowuje:

#include <assert.h>
#include <inttypes.h>
int main(void);
{
    T x[2];
    uintptr_t p = (uintptr_t)&x[0];
    uintptr_t q = (uintptr_t)&x[1];
    assert(q - p == sizeof(T)); // implementation-defined whether true
}

Ponieważ transformacja przeprowadzona przez konwersję wskaźnika do liczby całkowitej o tej samej wielkości lub odwrotnie, jest zdefiniowany implementację. I myślę jest wymagane, aby być bijective, ale mogłem się mylić, i zdecydowanie nie musi zachować żadnych z powyższych niezmienników.

0
zwol 5 czerwiec 2018, 18:40