Ausrichtung der Datenstruktur - Data structure alignment

Die Ausrichtung der Datenstruktur ist die Art und Weise, wie Daten im Computerspeicher angeordnet und darauf zugegriffen werden . Es besteht aus drei separaten, aber verwandten Themen: Datenausrichtung , Datenstrukturauffüllung und Packen .

Die CPU in moderner Computerhardware führt Lese- und Schreibvorgänge im Speicher am effizientesten aus, wenn die Daten natürlich ausgerichtet sind , was im Allgemeinen bedeutet, dass die Speicheradresse der Daten ein Vielfaches der Datengröße beträgt. Beispielsweise können in einer 32-Bit-Architektur die Daten ausgerichtet werden, wenn die Daten in vier aufeinanderfolgenden Bytes gespeichert sind und das erste Byte auf einer 4-Byte-Grenze liegt.

Datenausrichtung ist die Ausrichtung von Elementen gemäß ihrer natürlichen Ausrichtung. Um eine natürliche Ausrichtung zu gewährleisten, kann es erforderlich sein, zwischen Strukturelementen oder nach dem letzten Element einer Struktur etwas Polsterung einzufügen . Auf einer 32-Bit-Maschine könnte beispielsweise eine Datenstruktur, die einen 16-Bit-Wert gefolgt von einem 32-Bit-Wert enthält, 16 Bit Fülle zwischen dem 16-Bit-Wert und dem 32-Bit-Wert aufweisen, um die 32-Bit-Werte auszurichten Wert auf einer 32-Bit-Grenze. Alternativ kann man die Struktur packen und das Padding weglassen, was zu einem langsameren Zugriff führen kann, aber drei Viertel so viel Speicher verbraucht.

Obwohl die Datenstrukturausrichtung ein grundlegendes Problem für alle modernen Computer ist, handhaben viele Computersprachen und Computersprachenimplementierungen die Datenausrichtung automatisch. Fortran , Ada , PL/I , Pascal , bestimmte C- und C++- Implementierungen, D , Rust , C# und die Assemblersprache ermöglichen zumindest eine teilweise Kontrolle des Auffüllens von Datenstrukturen, was unter bestimmten besonderen Umständen nützlich sein kann.

Definitionen

Eine Speicheradresse a wird als n-Byte-ausgerichtet bezeichnet, wenn a ein Vielfaches von n Bytes ist (wobei n eine Potenz von 2 ist). Ein Byte ist dabei die kleinste Einheit des Speicherzugriffs, dh jede Speicheradresse gibt ein anderes Byte an. Eine n- Byte-ausgerichtete Adresse hätte ein Minimum von log 2 (n) am wenigsten signifikanten Nullen, wenn sie in binär ausgedrückt wird .

Der alternative Wortlaut b-bit-ausgerichtet bezeichnet eine b/8-Byte-ausgerichtete Adresse (z. B. 64-Bit- ausgerichtet ist 8-Byte-ausgerichtet).

Ein Speicherzugriff wird als ausgerichtet bezeichnet, wenn die Daten, auf die zugegriffen wird, n  Byte lang sind und die Datenadresse n- Byte ausgerichtet ist. Wenn ein Speicherzugriff nicht ausgerichtet ist, wird er als falsch ausgerichtet bezeichnet . Beachten Sie, dass Byte-Speicherzugriffe per Definition immer ausgerichtet sind.

Ein Speicherzeiger, der auf primitive Daten mit einer  Länge von n Byte verweist, wird als ausgerichtet bezeichnet, wenn er nur Adressen enthalten darf, die n- Byte-ausgerichtet sind, andernfalls wird er als nicht ausgerichtet bezeichnet . Ein Speicherzeiger, der auf ein Datenaggregat (eine Datenstruktur oder ein Array) verweist, wird ausgerichtet, wenn (und nur wenn) jedes primitive Datum im Aggregat ausgerichtet ist.

Beachten Sie, dass die obigen Definitionen davon ausgehen, dass jedes primitive Datum eine Potenz von zwei Bytes lang ist. Wenn dies nicht der Fall ist (wie bei 80-Bit-Gleitkomma auf x86 ), beeinflusst der Kontext die Bedingungen, unter denen das Datum als ausgerichtet betrachtet wird oder nicht.

Datenstrukturen können im Speicher auf dem Stack mit einer statischen Größe, die als begrenzt bezeichnet wird, oder auf dem Heap mit einer dynamischen Größe, die als unbegrenzt bezeichnet wird , gespeichert werden .

Probleme

Die CPU greift jeweils mit einem einzelnen Speicherwort auf den Speicher zu. Solange die Speicherwortgröße mindestens so groß ist wie der größte vom Computer unterstützte primitive Datentyp , greifen ausgerichtete Zugriffe immer auf ein einzelnes Speicherwort zu. Dies gilt möglicherweise nicht für falsch ausgerichtete Datenzugriffe.

Wenn sich das höchste und das niedrigste Byte in einem Datum nicht innerhalb desselben Speicherworts befinden, muss der Computer den Datumszugriff in mehrere Speicherzugriffe aufteilen. Dies erfordert einen hohen Schaltungsaufwand, um die Speicherzugriffe zu generieren und zu koordinieren. Um den Fall zu handhaben, in dem sich die Speicherwörter in verschiedenen Speicherseiten befinden, muss der Prozessor entweder verifizieren, dass beide Seiten vorhanden sind, bevor der Befehl ausgeführt wird, oder in der Lage sein, einen TLB- Fehltreffer oder einen Seitenfehler bei jedem Speicherzugriff während der Befehlsausführung zu behandeln.

Einige Prozessordesigns vermeiden bewusst die Einführung einer solchen Komplexität und ergeben stattdessen ein alternatives Verhalten im Falle eines falsch ausgerichteten Speicherzugriffs. Zum Beispiel erfordern Implementierungen der ARM-Architektur vor der ARMv6-ISA einen obligatorischen ausgerichteten Speicherzugriff für alle Mehrbyte-Lade- und -Speicherbefehle. Je nachdem, welcher spezifische Befehl ausgegeben wurde, kann das Ergebnis eines falsch ausgerichteten Zugriffs sein, dass die niedrigstwertigen Bits der fehlerhaften Adresse abgerundet werden, wodurch sie in einen ausgerichteten Zugriff umgewandelt werden (manchmal mit zusätzlichen Einschränkungen) oder eine MMU-Ausnahme ausgelöst wird (wenn MMU-Hardware vorhanden ist) oder stillschweigend andere potenziell unvorhersehbare Ergebnisse liefern. Die Architekturen von ARMv6 und höher unterstützen in vielen Fällen nicht ausgerichteten Zugriff, jedoch nicht unbedingt in allen.

Wenn auf ein einzelnes Speicherwort zugegriffen wird, ist die Operation atomar, dh das gesamte Speicherwort wird auf einmal gelesen oder geschrieben und andere Geräte müssen warten, bis die Lese- oder Schreiboperation abgeschlossen ist, bevor sie darauf zugreifen können. Dies gilt möglicherweise nicht für unausgerichtete Zugriffe auf mehrere Speicherwörter, z. B. könnte das erste Wort von einem Gerät gelesen werden, beide Wörter von einem anderen Gerät geschrieben und dann das zweite Wort vom ersten Gerät gelesen, so dass der gelesene Wert weder der Originalwert ist noch der aktualisierte Wert. Obwohl solche Fehler selten sind, können sie sehr schwer zu identifizieren sein.

Auffüllen der Datenstruktur

Obwohl der Compiler (oder Interpreter ) normalerweise einzelne Datenelemente auf ausgerichteten Grenzen zuordnet, haben Datenstrukturen oft Mitglieder mit unterschiedlichen Ausrichtungsanforderungen. Um die richtige Ausrichtung beizubehalten, fügt der Übersetzer normalerweise zusätzliche unbenannte Datenelemente ein, so dass jedes Element richtig ausgerichtet ist. Außerdem kann die Datenstruktur als Ganzes mit einem letzten unbenannten Member aufgefüllt werden. Dadurch kann jedes Mitglied eines Arrays von Strukturen richtig ausgerichtet werden.

Padding wird nur eingefügt, wenn einem Strukturelement ein Element mit einer größeren Ausrichtungsanforderung folgt oder am Ende der Struktur. Durch Ändern der Reihenfolge der Elemente in einer Struktur ist es möglich, den zum Beibehalten der Ausrichtung erforderlichen Abstand zu ändern. Wenn beispielsweise Elemente nach absteigenden Ausrichtungsanforderungen sortiert werden, ist nur minimales Auffüllen erforderlich. Die minimal erforderliche Polsterung ist immer kleiner als die größte Ausrichtung in der Struktur. Die Berechnung der maximal erforderlichen Auffüllung ist komplizierter, aber immer kleiner als die Summe der Ausrichtungsanforderungen für alle Elemente minus der doppelten Summe der Ausrichtungsanforderungen für die am wenigsten ausgerichtete Hälfte der Strukturelemente.

Obwohl C und C++ es dem Compiler nicht erlauben, Strukturmember neu anzuordnen, um Platz zu sparen, könnten andere Sprachen dies tun. Es ist auch möglich, die meisten C- und C++-Compiler anzuweisen, die Member einer Struktur auf eine bestimmte Ausrichtungsebene zu "packen", z alle Füllelemente sind höchstens ein Byte lang. In ähnlicher Weise kann in PL/I eine Struktur deklariert werden UNALIGNED, um alle Auffüllungen außer um Bitketten herum zu eliminieren.

Eine Verwendung für solche "gepackten" Strukturen besteht darin, Speicher zu sparen. Beispielsweise würde eine Struktur, die ein einzelnes Byte und eine Vier-Byte-Ganzzahl enthält, drei zusätzliche Bytes zum Auffüllen erfordern. Ein großes Array solcher Strukturen würde 37,5% weniger Speicher verbrauchen, wenn sie gepackt sind, obwohl der Zugriff auf jede Struktur länger dauern könnte. Dieser Kompromiss kann als eine Form von Raum-Zeit-Kompromiss angesehen werden .

Obwohl die Verwendung von "gepackten" Strukturen am häufigsten verwendet wird, um Speicherplatz zu sparen , kann sie auch verwendet werden, um eine Datenstruktur zur Übertragung unter Verwendung eines Standardprotokolls zu formatieren. Bei dieser Verwendung muss jedoch auch darauf geachtet werden, dass die Werte der Strukturelemente mit der vom Protokoll geforderten Endianness (oft Netzwerk-Byte-Reihenfolge ) gespeichert werden , die sich von der nativ vom Host-Rechner verwendeten Endianness unterscheiden kann.

Computerpolsterung

Die folgenden Formeln geben die Anzahl der Padding-Bytes an, die erforderlich sind, um den Anfang einer Datenstruktur auszurichten (wobei mod der Modulo- Operator ist):

padding = (align - (offset mod align)) mod align
aligned = offset + padding
        = offset + ((align - (offset mod align)) mod align)

Zum Beispiel kann das Polster hinzuzufügen , für eine 4-Byte - ausgerichtete Struktur auf Offset 0x59d wird 3. Die Struktur wird dann bei 0x5a0 beginnen, was ein Vielfaches von 4 ist , jedoch , wenn die Ausrichtung von Offset bereits zu der gleich align , das zweite Modulo in (align - (offset mod align)) mod align gibt null zurück, daher bleibt der ursprüngliche Wert unverändert.

Da die Ausrichtung per Definition eine Zweierpotenz ist, kann die Modulo-Operation auf eine bitweise boolesche UND-Operation reduziert werden.

Die folgenden Formeln erzeugen den ausgerichteten Offset (wobei & ein bitweises AND und ~ ein bitweises NOT ist ):

padding = (align - (offset & (align - 1))) & (align - 1)
        = (-offset & (align - 1))
aligned = (offset + (align - 1)) & ~(align - 1)
        = (offset + (align - 1)) & -align

Typische Ausrichtung von C-Strukturen auf x86

Datenstrukturelemente werden sequentiell im Speicher gespeichert, so dass in der Struktur unten das Element Data1 immer Data2 vorausgeht; und Data2 wird immer Data3 vorausgehen:

struct MyData
{
    short Data1;
    short Data2;
    short Data3;
};

Wenn der Typ "kurz" in zwei Bytes des Speichers gespeichert wird, dann würde jedes Mitglied der oben abgebildeten Datenstruktur auf 2 Byte ausgerichtet sein. Data1 wäre bei Offset 0, Data2 bei Offset 2 und Data3 bei Offset 4. Die Größe dieser Struktur würde 6 Byte betragen.

Der Typ jedes Elements der Struktur hat normalerweise eine Standardausrichtung, was bedeutet, dass er, sofern vom Programmierer nicht anders gewünscht, an einer vorbestimmten Grenze ausgerichtet wird. Die folgenden typischen Ausrichtungen gelten für Compiler von Microsoft ( Visual C++ ), Borland / CodeGear ( C++Builder ), Digital Mars (DMC) und GNU ( GCC ) beim Kompilieren für 32-Bit x86:

  • Ein char (ein Byte) wird 1-Byte ausgerichtet.
  • Ein Short (zwei Bytes) wird auf 2 Byte ausgerichtet.
  • Ein int (vier Bytes) wird auf 4 Byte ausgerichtet.
  • Ein Long (vier Bytes) wird auf 4 Byte ausgerichtet.
  • Ein Float (vier Bytes) wird auf 4 Byte ausgerichtet.
  • Ein Double (acht Bytes) wird unter Windows auf 8 Byte ausgerichtet und unter Linux auf 4 Byte ausgerichtet (8 Byte mit der Kompilierzeitoption -malign-double ).
  • Ein Long Long (acht Bytes) wird auf 4 Byte ausgerichtet.
  • Ein langes Double (zehn Byte bei C++Builder und DMC, acht Byte bei Visual C++, zwölf Byte bei GCC) wird 8-Byte-ausgerichtet mit C++Builder, 2-Byte-ausgerichtet mit DMC, 8-Byte-ausgerichtet mit Visual C++ und 4-Byte ausgerichtet mit GCC.
  • Jeder Zeiger (vier Bytes) wird auf 4 Byte ausgerichtet. (zB: char*, int*)

Die einzigen bemerkenswerten Unterschiede in der Ausrichtung eines LP64 64-Bit-Systems im Vergleich zu einem 32-Bit-System sind:

  • Ein Long (acht Bytes) wird auf 8 Byte ausgerichtet.
  • Ein Double (acht Byte) wird auf 8 Byte ausgerichtet.
  • Ein Long Long (acht Bytes) wird auf 8 Byte ausgerichtet.
  • Ein langes Double (acht Byte bei Visual C++, sechzehn Byte bei GCC) wird 8-Byte mit Visual C++ und 16-Byte mit GCC ausgerichtet.
  • Jeder Zeiger (acht Byte) wird auf 8 Byte ausgerichtet.

Einige Datentypen sind von der Implementierung abhängig.

Hier ist eine Struktur mit Mitgliedern verschiedener Typen, insgesamt 8 Bytes vor der Kompilierung:

struct MixedData
{
    char Data1;
    short Data2;
    int Data3;
    char Data4;
};

Nach der Kompilierung wird die Datenstruktur mit Füllbytes ergänzt, um eine korrekte Ausrichtung für jedes ihrer Mitglieder zu gewährleisten:

struct MixedData  /* After compilation in 32-bit x86 machine */
{
    char Data1; /* 1 byte */
    char Padding1[1]; /* 1 byte for the following 'short' to be aligned on a 2 byte boundary
assuming that the address where structure begins is an even number */
    short Data2; /* 2 bytes */
    int Data3;  /* 4 bytes - largest structure member */
    char Data4; /* 1 byte */
    char Padding2[3]; /* 3 bytes to make total size of the structure 12 bytes */
};

Die kompilierte Größe der Struktur beträgt jetzt 12 Byte. Es ist wichtig zu beachten, dass das letzte Element mit der erforderlichen Anzahl von Bytes aufgefüllt wird, sodass die Gesamtgröße der Struktur ein Vielfaches der größten Ausrichtung aller Strukturelemente sein sollte (in diesem Fall alignment(int), was = 4 auf . ist linux-32bit/gcc).

In diesem Fall werden dem letzten Member 3 Byte hinzugefügt, um die Struktur auf die Größe von 12 Byte (alignment(int) × 3) aufzufüllen.

struct FinalPad {
  float x;
  char n[1];
};

In diesem Beispiel ist die Gesamtgröße der Struktur sizeof (FinalPad) == 8 , nicht 5 (damit die Größe ein Vielfaches von 4 ist (Ausrichtung von Float)).

struct FinalPadShort {
  short s;
  char n[3];
};

In diesem Beispiel ist die Gesamtgröße der Struktur sizeof (FinalPadShort) == 6 , nicht 5 (auch nicht 8) (so dass die Größe ein Vielfaches von 2 ist (alignment(short) = 2 on linux-32bit/gcc)).

Es ist möglich, die Ausrichtung von Strukturen zu ändern, um den benötigten Speicher zu reduzieren (oder um einem bestehenden Format zu entsprechen), indem Strukturelemente neu angeordnet werden oder die Ausrichtung des Compilers (oder „Packen“) von Strukturelementen geändert wird.

struct MixedData  /* after reordering */
{
    char Data1;
    char Data4;   /* reordered */
    short Data2;
    int Data3;
};

Die kompilierte Größe der Struktur entspricht jetzt der vorkompilierten Größe von 8 Byte . Beachten Sie, dass Padding1[1] durch Data4 ersetzt (und damit eliminiert) wurde und Padding2[3] nicht mehr erforderlich ist, da die Struktur bereits auf die Größe eines langen Wortes ausgerichtet ist.

Die alternative Methode zum Erzwingen der Ausrichtung der MixedData- Struktur an einer Ein-Byte-Grenze führt dazu, dass der Präprozessor die vorbestimmte Ausrichtung der Strukturelemente verwirft und somit keine Auffüllbytes eingefügt werden.

Obwohl es keine Standardmethode zum Definieren der Ausrichtung von Strukturmembern gibt, verwenden einige Compiler #pragma- Direktiven, um das Packen in Quelldateien anzugeben. Hier ist ein Beispiel:

#pragma pack(push)  /* push current alignment to stack */
#pragma pack(1)     /* set alignment to 1 byte boundary */

struct MyPackedData
{
    char Data1;
    long Data2;
    char Data3;
};

#pragma pack(pop)   /* restore original alignment from stack */

Diese Struktur hätte eine kompilierte Größe von 6 Byte auf einem 32-Bit-System. Die obigen Direktiven sind in Compilern von Microsoft , Borland , GNU und vielen anderen verfügbar .

Ein anderes Beispiel:

struct MyPackedData
{
    char Data1;
    long Data2;
    char Data3;
} __attribute__((packed));

Standardverpackung und #pragma-Paket

Bei einigen Microsoft-Compilern, insbesondere bei RISC-Prozessoren, besteht eine unerwartete Beziehung zwischen der Projektstandardverpackung (der /Zp-Direktive) und der #pragma pack- Direktive. Die #pragma pack- Direktive kann nur verwendet werden, um die Verpackungsgröße einer Struktur aus der Standardverpackung des Projekts zu reduzieren . Dies führt zu Interoperabilitätsproblemen mit Bibliotheksheadern, die beispielsweise #pragma pack(8) verwenden , wenn das Projektpaket kleiner ist. Aus diesem Grund würde das Festlegen des Projektpackings auf einen anderen Wert als den Standardwert von 8 Bytes die #pragma pack- Direktiven, die in Bibliotheksheadern verwendet werden, brechen und zu binären Inkompatibilitäten zwischen Strukturen führen. Diese Einschränkung ist beim Kompilieren für x86 nicht vorhanden.

Zuweisen von Speicher, der auf Cache-Zeilen ausgerichtet ist

Es wäre vorteilhaft, den Cache-Zeilen ausgerichteten Speicher zuzuweisen . Wenn ein Array so partitioniert ist, dass mehr als ein Thread bearbeitet werden kann, kann es zu einer Leistungsverschlechterung führen, wenn die Subarraygrenzen nicht an Cachezeilen ausgerichtet sind. Hier ist ein Beispiel für die Zuweisung von Speicher (Doppelarray der Größe 10), ausgerichtet auf einen Cache von 64 Bytes.

#include <stdlib.h>
double *foo(void) {
   double *var;//create array of size 10
   int     ok;

   ok = posix_memalign((void**)&var, 64, 10*sizeof(double));

   if (ok != 0)
     return NULL;

   return var;
}

Hardwarebedeutung der Ausrichtungsanforderungen

Ausrichtungsbedenken können Bereiche betreffen, die viel größer als eine C-Struktur sind, wenn der Zweck die effiziente Abbildung dieses Bereichs durch einen Hardware- Adressübersetzungsmechanismus (PCI-Neuabbildung, Betrieb einer MMU ) ist.

Auf einem 32-Bit-Betriebssystem ist beispielsweise eine Seite mit 4  KiB (4096 Bytes) nicht nur ein willkürlicher 4 KiB-Datenblock. Stattdessen handelt es sich normalerweise um einen Speicherbereich, der an einer 4-KB-Grenze ausgerichtet ist. Dies liegt daran, dass das Ausrichten einer Seite an einer seitengroßen Grenze es der Hardware ermöglicht, eine virtuelle Adresse auf eine physikalische Adresse abzubilden, indem die höheren Bits in der Adresse ersetzt werden, anstatt komplexe Arithmetik durchzuführen.

Beispiel: Angenommen, wir haben eine TLB-Zuordnung der virtuellen Adresse 0x2CFC7000 zur physischen Adresse 0x12345000. (Beachten Sie, dass diese beiden Adressen an 4 KiB-Grenzen ausgerichtet sind.) Der Zugriff auf Daten, die sich an der virtuellen Adresse va=0x2CFC7ABC befinden, bewirkt, dass eine TLB-Auflösung von 0x2CFC7 bis 0x12345 einen physischen Zugriff auf pa=0x12345ABC ausgibt. Hier stimmt die 20/12-Bit-Aufteilung glücklicherweise mit der hexadezimalen Darstellung auf 5/3-Stellen überein. Die Hardware kann diese Übersetzung implementieren, indem sie einfach die ersten 20 Bit der physikalischen Adresse (0x12345) und die letzten 12 Bit der virtuellen Adresse (0xABC) kombiniert. Dies wird auch als virtuell indiziert (ABC) physikalisch markiert (12345) bezeichnet.

Ein Datenblock der Größe 2 (n + 1) – 1 hat immer einen Unterblock der Größe 2 n  , der auf 2 n  Bytes ausgerichtet ist.

Auf diese Weise kann ein dynamischer Allokator, der keine Kenntnisse über die Ausrichtung hat, verwendet werden, um ausgerichtete Puffer zum Preis eines Platzverlusts um den Faktor zwei bereitzustellen.

// Example: get 4096 bytes aligned on a 4096 byte buffer with malloc()

// unaligned pointer to large area
void *up = malloc((1 << 13) - 1);
// well-aligned pointer to 4 KiB
void *ap = aligntonext(up, 12);

wobei aligntonext( p , r ) funktioniert, indem es ein ausgerichtetes Inkrement hinzufügt und dann die r am wenigsten signifikanten Bits von p löscht . Eine mögliche Implementierung ist

// Assume `uint32_t p, bits;` for readability
#define alignto(p, bits)      (((p) >> bits) << bits)
#define aligntonext(p, bits)  alignto(((p) + (1 << bits) - 1), bits)

Anmerkungen

Verweise

Weiterlesen

  • Bryant, Randal E .; David, O'Hallaron (2003). Computersysteme: Die Perspektive eines Programmierers (Ausgabe 2003). Upper Saddle River, New Jersey, USA: Pearson Education . ISBN 0-13-034074-X.
  • "1. Einführung: Segmentausrichtung". 8086 Family Utilities - Benutzerhandbuch für 8080/8085-basierte Entwicklungssysteme (PDF) . Revision E (A620/5821 6K DD-Ausgabe). Santa Clara, Kalifornien, USA: Intel Corporation . Mai 1982 (1980, 1978). S. 1-6, 3-5. Bestellnummer: 9800639-04. Archiviert (PDF) vom Original am 29.02.2020 . Abgerufen 2020-02-29 . […] Ein Segment kann eines (und im Fall des Inpage-Attributs zwei) von fünf Alignment-Attributen haben: […] Byte, d. h. ein Segment kann sich an einer beliebigen Adresse befinden. […] Wort, d.h. ein Segment kann nur an einer Adresse liegen, die ein Vielfaches von zwei ist, beginnend mit Adresse 0H. […] Absatz, was bedeutet, dass sich ein Segment nur an einer Adresse befinden kann, die ein Vielfaches von 16 ist, beginnend mit Adresse 0. […] Seite, was bedeutet, dass ein Segment nur an einer Adresse lokalisiert werden kann, die ein Vielfaches von 256 ist , beginnend bei Adresse 0. […] Inpage, d. h. ein Segment kann sich an jedem der vorstehenden Attribute befinden, plus muss so platziert werden, dass es keine Seitengrenze überschreitet […] Die Ausrichtungscodes sind: […] B - Byte […] W - Wort […] G - Absatz […] xR - Inpage […] P - Seite […] A - Absolut […] Das x im Inpage-Alignment-Code kann ein beliebiger anderer Alignment-Code sein. […] ein Segment kann das Inpage-Attribut haben, d. h. es muss sich innerhalb einer 256-Byte-Seite befinden und kann das Wort-Attribut haben, d. h. es muss sich auf einem geraden Byte befinden. […]

Externe Links