Frage

, fragte ich eine Frage etwa C-Typ-Größen die ich eine ziemlich gute Antwort, aber ich merkte, dass ich die Frage nicht sehr gut formulieren kann für meine Zwecke nützlich sein.

Mein Hintergrund war von Ingenieur-Computer vor bewegt zu Software Engineer so wie ich Computerarchitekturen und denke immer an VM zu machen. Ich habe gerade ein interessantes Projekt zu machen VM auf Java, die ich ganz mit stolz bin. Aber es gibt einige rechtliche Probleme kann ich nicht Open-Source es jetzt, und ich bin zur Zeit haben einige freie Zeit. Deshalb möchte ich sehen, ob ich eine andere VM auf C (mit einer besseren Geschwindigkeit) machen nur zum Spaß und lehrreich.

Die Sache ist, ich bin kein C-Programm das letzte Mal, dass ich ein nicht-Trivia C Problem vor mehr als 10 Jahren war geschrieben. Ich war Pascal, Delphi, und jetzt Java und PHP-Programmierer.

Es gibt eine Reihe von Hindernissen ich vorhersehen kann, und ich versuche, eine zu bekämpfen, und das ist die Nutzung bereits vorhandene Bibliothek (in Java, Reflexion löst dieses Problem).

Ich plane, dieses Problem zu lösen, indem einen Puffer von Daten (ähnlich zu stapeln) mit. Der Kunde meiner VM kann Daten in diesen Stapel programmieren setzen, bevor Sie mir geben, um native Funktion auf Zeiger.

int main(void) {
    // Prepare stack
    int   aStackSize = 1024*4;
    char *aStackData = malloc(aStackSize);

    // Initialise stack
    VMStack aStack;
    VMStack_Initialize(&aStack, (char *)aStackData, aStackSize);

    // Push in the parameters
    char *Params = VMStack_CurrentPointer(&aStack);
    VMStack_Push_int   (&aStack, 10  ); // Push an int
    VMStack_Push_double(&aStack, 15.3); // Push a double

    // Prepare space for the expected return
    char *Result = VMStack_CurrentPointer(&aStack);
    VMStack_Push_double(&aStack, 0.0); // Push an empty double for result

    // Execute
    void (*NativeFunction)(char*, char*) = &Plus;
    NativeFunction(Params, Result); // Call the function

    // Show the result
    double ResultValue = VMStack_Pull_double(&aStack); // Get the result
    printf("Result:  %5.2f\n", ResultValue);               // Print the result

    // Remove the previous parameters
    VMStack_Pull_double(&aStack); // Pull to clear space of the parameter
    VMStack_Pull_int   (&aStack); // Pull to clear space of the parameter

    // Just to be sure, print out the pointer and see if it is `0`
    printf("Pointer: %d\n", aStack.Pointer);

    free(aStackData);
    return EXIT_SUCCESS;
}

Der schieben, ziehen und Aufruf von native Funktion kann durch ein Byte-Code ausgelöst werden (das ist, wie VM wird später vorgenommen werden).

Aus Gründen der Vollständigkeit (so dass Sie es auf Sie Maschine ausprobieren können), hier ist der Code für Stack:

typedef struct {
    int  Pointer;
    int  Size;
    char *Data;
} VMStack;

inline void   VMStack_Initialize(VMStack *pStack, char *pData, int pSize) __attribute__((always_inline));
inline char   *VMStack_CurrentPointer(VMStack *pStack)                    __attribute__((always_inline));
inline void   VMStack_Push_int(VMStack *pStack, int pData)                __attribute__((always_inline));
inline void   VMStack_Push_double(VMStack *pStack, double pData)          __attribute__((always_inline));
inline int    VMStack_Pull_int(VMStack *pStack)                           __attribute__((always_inline));
inline double VMStack_Pull_double(VMStack *pStack)                        __attribute__((always_inline));

inline void VMStack_Initialize(VMStack *pStack, char *pData, int pSize) {
    pStack->Pointer = 0;
    pStack->Data    = pData;
    pStack->Size    = pSize;
}

inline char *VMStack_CurrentPointer(VMStack *pStack) {
    return (char *)(pStack->Pointer + pStack->Data);
}

inline void VMStack_Push_int(VMStack *pStack, int pData) {
    *(int *)(pStack->Data + pStack->Pointer) = pData;
    pStack->Pointer += sizeof pData; // Should check the overflow
}
inline void VMStack_Push_double(VMStack *pStack, double pData) {
    *(double *)(pStack->Data + pStack->Pointer) = pData;
    pStack->Pointer += sizeof pData; // Should check the overflow
}

inline int VMStack_Pull_int(VMStack *pStack) {
    pStack->Pointer -= sizeof(int);// Should check the underflow
    return *((int *)(pStack->Data + pStack->Pointer));
}
inline double VMStack_Pull_double(VMStack *pStack) {
    pStack->Pointer -= sizeof(double);// Should check the underflow
    return *((double *)(pStack->Data + pStack->Pointer));
}

Auf der native Funktion Seite, habe ich folgendes zu Testzwecken:

// These two structures are there so that Plus will not need to access its parameter using
//    arithmetic-pointer operation (to reduce mistake and hopefully for better speed).
typedef struct {
    int    A;
    double B;
} Data;
typedef struct {
    double D;
} DDouble;

// Here is a helper function for displaying void PrintData(Data *pData, DDouble *pResult) { printf("%5.2f + %5.2f = %5.2f\n", pData->A*1.0, pData->B, pResult->D); }

// Some native function void Plus(char* pParams, char* pResult) { Data *D = (Data *)pParams; // Access data without arithmetic-pointer operation DDouble *DD = (DDouble *)pResult; // Same for return DD->D = D->A + D->B; PrintData(D, DD); }

Wenn er ausgeführt wird, der obige Code zurückgibt:

10.00 + 15.30 = 25.30
Result:  25.30
Pointer: 0

Diese Arbeit gut auf meinem Rechner (Linux x86 32-Bit-GCC-C99). Es wird sehr schön, wenn diese auf anderen OS / Architektur auch funktioniert. Aber es gibt mindestens drei speicherbezogene issures müssen wir sich bewusst sein.

1). Datengröße -. Es scheint, als wenn ich beide VM und native Funktionen kompilieren den gleichen Compiler auf der gleichen Architektur verwendet wird, sollte die Größe Typen gleich sein

2). Endian -. Das Gleiche gilt für Datengröße

3). Speicherausrichtung -. Welches ist das Problem als padding-Bytes ist, kann in Struktur hinzugefügt werden, aber es ist schwer, es zu synchronisieren, wenn Parameterstapel herzustellen, wie (es gibt keine Möglichkeit zu wissen, wie padding hinzugefügt wird, außer für harte Codierung)

Meine Fragen sind:

1). Wenn ich die Größe der Typen weiß, ist es eine Möglichkeit, Push zu ändern und die Funktion zu ziehen, um genau mit struct padding zu synchronisieren? (Ändern lassen Compiler kümmert sich um sie wie Datasize und Endians Probleme).

2). Wenn ich einpacken Struktur durch eine (mit #pragma pack(1)); (2.1) Wird die Leistungseinbuße akzeptabel? und (2.2) Wird die Programmstabilität gefährdet sein?

3). Wie wäre es Polsterung von 2,4 oder 8? Die für die allgemeine 32 oder 64 Bit System sollte gut sein?

4). Können Sie mir eine Dokumentation für eine exakte padding-Algorithmus sagen wir mal für GCC auf x86 anleitet?

5). Gibt es einen besseren Weg?

Hinweis: Plattformübergreifender es ist nicht mein Ziel, aber ich kann nicht widerstehen. Auch die Leistung ist nicht mein Ziel, sobald es nicht so hässlich ist. All dies ist für Spaß und Lernen.

Sorry für mein Englisch und die sehr langen Pfosten.

Danke an alle im Voraus.

War es hilfreich?

Lösung

Tangential Kommentare

Diese ersten Produkte sind tangential zu den Fragen, die Sie gefragt, aber ...

// Execute
void (*NativeFunction)(char*, char*) = &Plus;
NativeFunction(Params, Result); // Call the function

Ich glaube, Sie sollten wahrscheinlich ‚void *‘ anstelle von ‚char *‘ verwenden hier. Ich würde auch ein typedef für die Funktion Zeigertyp haben:

typedef void (*Operator)(void *params, void *result);

Dann können Sie schreiben:

Operator NativeFunction = Plus;

Die eigentliche Funktion wäre zu geändert werden - aber nur sehr gering:

void Plus(void *pParams, void *pResult)

Außerdem haben Sie eine kleine Namens Problem - diese Funktion ist ‚IntPlusDoubleGivesDouble ()‘, sondern als ein Allzweck ‚fügen Sie alle zwei Typen‘ Funktion

.

Direkte Antworten auf die Fragen

  

1). Wenn ich die Größe der Typen weiß, ist es eine Möglichkeit, Push zu ändern und die Funktion zu ziehen, um genau mit struct padding zu synchronisieren? (Ändern lassen Compiler kümmert sich um sie wie Datasize und Endians Probleme).

Es gibt keine einfache Möglichkeit, das zu tun. Betrachten wir zum Beispiel:

struct Type1
{
     unsigned char byte;
     int           number;
};
struct Type2
{
     unsigned char byte;
     double        number;
};

Bei einigen Architekturen (32-Bit- oder 64-Bit-SPARC, zum Beispiel), wird die Struktur hat Typ1 ‚Zahl‘ ausgerichtet an einer 4-Byte-Grenze, aber die Type2 Struktur hat ‚Zahl‘ ausgerichtet auf einer 8- Byte-Grenze (und vielleicht eine 'long double' auf einem 16-Byte-Grenze haben). Ihre "Push einzelnen Elemente der Strategie würde stoßen den Stapelzeiger um 1 nach dem‚byte‘Wert schieben - so können Sie den Stapelzeiger durch 3 oder 7 vor Drücken der‚Zahl‘bewegen wollen würde, wenn der Stapelzeiger nicht bereits ist angemessen ausgerichtet. Ein Teil Ihrer VM Beschreibung werden die erforderlichen Anordnungen für einen bestimmten Typ sein; der entsprechende Push-Code muss die korrekte Ausrichtung gewährleisten, bevor drücken.

  

2). Wenn ich Packungsstruktur durch eine (#pragma pack unter Verwendung (1)); (2.1) Wird die Leistungseinbuße akzeptabel? und (2.2) Wird die Programmstabilität gefährdet sein?

Auf x86 und x86_64 Maschinen, wenn Sie die Daten packen, werden Sie eine Leistungseinbuße für den falsch ausgerichteten Datenzugriff entstehen. Bei Maschinen wie SPARC oder PowerPC (pro mecki ), werden Sie einen Bus-Fehler erhalten oder etwas ähnliches statt - müssen Sie die Daten in der richtigen Ausrichtung zuzugreifen. Sie könnten etwas Speicherplatz sparen - auf Kosten der Leistung. Sie würden besser tun Leistung zu gewährleisten (das hier beinhaltet ‚richtig statt Absturz performing‘) an den Grenzkosten im Raum.

  

3). Wie wäre es Polsterung von 2,4 oder 8? Die für die allgemeine 32 oder 64 Bit System sollte gut sein?

Auf SPARC, müssen Sie Pad einen N-Byte-Basistyp zu einer N-Byte-Grenze. Auf x86, werden Sie die beste Leistung, wenn man das gleiche tun.

  

4). Können Sie mir eine Dokumentation für eine exakte padding-Algorithmus führen wir für GCC auf x86 sagen?

Sie würden das Handbuch lesen.

  

5). Gibt es einen besseren Weg?

Beachten Sie, dass der Trick ‚Typ1‘ mit einem einzelnen Zeichen gefolgt von einem Typ gibt Ihnen die Anforderung Ausrichtung - möglicherweise den ‚offsetof ()‘ Makro von <stddef.h> mit:

offsetof(struct Type1, number)

Nun, ich würde nicht auf die Daten auf dem Stack packen - ich würde mit der nativen Ausrichtung arbeiten, weil das eingestellt wird, um die beste Leistung zu geben. Der Compiler Schriftsteller nicht untätig hinzufügen Polsterung an einer Struktur; sie setzen es dort, weil es ‚beste‘ für die Architektur arbeitet. Wenn Sie sich entscheiden, Sie wissen besser, können Sie die üblichen Konsequenzen rechnen -. Langsame Programme, die manchmal nicht und sind nicht als tragbares

Ich bin auch nicht überzeugt, dass ich den Code in den Operatorfunktionen schreiben würde davon ausgehen, dass der Stapel eine Struktur enthalten. Ich würde die Werte vom Stapel über das Params Argument ziehen, zu wissen, was die richtigen Offsets und die Typen waren. Wenn ich eine ganze Zahl und ein Doppel geschoben, dann würde ich eine ganze Zahl und ein Doppel ziehen (oder kannsein, in umgekehrter Reihenfolge - ich einen Doppel- und ein int ziehen würde). Sofern Sie eine ungewöhnliche VM planen, einige Funktionen werden viele Argumente haben.

Andere Tipps

Interessante Post und zeigt, dass Sie in einer Menge Arbeit gesteckt haben. Fast die ideale SO stellen.

Ich habe keine fertigen Antworten, so wenden Sie sich bitte mit mir tragen. Ich werde noch ein paar Fragen zu stellen haben: P

  

1). Wenn ich die Größe der Typen weiß, ist es eine Möglichkeit, Push zu ändern und die Funktion zu ziehen, um genau mit struct padding zu synchronisieren? (Ändern lassen Compiler kümmert sich um sie wie Datasize und Endians Probleme).

Ist das von einer Performance-Sicht nur? Planen Sie Zeiger zusammen mit nativen Rechenarten einzuführen?

  

2). Wenn ich Packungsstruktur durch eine (#pragma pack unter Verwendung (1)); (2.1) Wird die Leistungseinbuße akzeptabel? und (2.2) Wird die Programmstabilität gefährdet sein?

Dies ist eine Implementierung definiert Sache. Nicht etwas, das Sie auf allen Plattformen zählen können.

  

3). Wie wäre es Polsterung von 2,4 oder 8? Die für die allgemeine 32 oder 64 Bit System sollte gut sein?

Der Wert, mit der nativen Wortgröße paßt sollten Ihnen eine optimale Leistung geben.

  

4). Können Sie mir eine Dokumentation für eine exakte padding-Algorithmus sagen wir mal für GCC auf x86 anleitet?

Ich weiß nicht von irgendwelchen der Spitze von meinem Kopf. Aber ich habe Code ähnlich gesehen diese verwendet wird.

Beachten Sie, dass Sie angeben Attribute von Variablen GCC (die auch etwas default_struct __attribute__((packed)) genannt hat, die Polsterung schaltet sich aus).

Es gibt einige sehr gute Fragen hier, werden viele von ihnen in einigen wichtigen Designfragen verheddern, aber für die meisten von uns - wir können sehen, was Sie arbeiten auf (dirkgently nur geschrieben, wie ich schreibe, so können Sie sehen, Sie generieren Interesse) können wir Ihr Englisch gut genug, um verstehen, dass zu dem, was Sie gerade arbeiten einige Compiler Probleme und einige Sprach Design-Probleme - es wird schwierig, die Frage zu arbeiten, aber, dass Sie in JNI arbeiten bereits gibt es Hoffnung ...

Für eine Sache, würde ich versuchen, von Pragmas wegzukommen; Viele Leute, sehr viele wird damit nicht einverstanden sind. Für kanonische Diskussion darüber, warum in der Frage der Rechtfertigung für die D Sprache Position sehen. Zum anderen gibt es eine 16-Bit-Zeiger in Ihrem Code begraben.

Die Probleme sind in der Nähe von endlosem, gut untersucht, und wahrscheinlich zu uns in Opposition und intra-Unnachgiebigkeit begraben zu lassen. wenn ich könnte darauf hindeuten, sowie die Intel-Architektur Handbuch Kenneth Louden der Homepage zu lesen. Ich habe es, ich habe versucht, es zu lesen. Datenstruktur Ausrichtung, zusammen mit vielen anderen Fragen, die Sie zur Diskussion gestellt werden tief in historischen Compiler Wissenschaft begraben und sind wahrscheinlich in Sie überschwemmt zu bekommen, wer was weiß. (Slang oder idiomatische für unvorhergesehene Fragen der Folge)

Mit dieser sagte, hier geht:

  1. C-Typ-Größen Welche Größe?
  2. Computertechniker vor bewegt sich Software Engineer Immer Mikrocontroller untersucht? Hava ein Blick auf einige von Don Lancaster Arbeit.
  3. Pascal, Delphi, und jetzt Java und PHP Programmierer. Diese sind vergleichsweise von der Basis grundlegende Architektur der Prozessoren entfernt, obwohl viele Personen zeigen oder versuchen zu zeigen, wie sie verwendet werden können leistungsfähige und grundlegende Routinen zu schreiben. Ich schlage vor, Blick auf David Ecks Rekursiver Abstieg genau zu sehen, wie Untersuchung der Angelegenheit zu beginnen. Wie gut, hat Kenneth Louden eine Implementierung von „Tiny“, die ein tatsächliches Compiler ist. Ich fand auch etwas nicht lange her, dass ich denke, wurde asm Punkt genannt org ... sehr weit fortgeschritten, sehr leistungsfähige Arbeit dort für Studie verfügbar war, aber es ist ein langer Weg in Assembler Schreiben zu beginnen beabsichtigen in Compiler Wissenschaft zu bekommen. Darüber hinaus haben die meisten Architekturen Unterschiede, die nicht konsistent von einem Prozessor zum anderen sind.
  4. Zugriff auf vorhandene Bibliothek

Es gibt viele Libs um, Java hat einige gute. Ich weiß nicht, über die andere. Ein Ansatz besteht darin zu versuchen, eine lib zu schreiben. Java hat eine gute Basis und lässt Raum für die Menschen besser mit etwas einfallen lassen, um zu versuchen mögen. Beginnen Sie mit der Verbesserung Knuth-Morris-Pratt oder etwas: Es gibt einfach keinen Mangel an Plätzen zu beginnen. Versuchen Sie Computer Programming Algorithmen Verzeichnis und sicher, Blick auf Wörterbuch von Algorithmen und Datenstrukturen am NIST

  1. always_inline

Nicht unbedingt, siehe Dov Bulka - der Arbeiter einen Doktortitel in CS hält und ebenso ist ein kompetenter Autor in Bereichen, in denen zeit Effizienz / Zuverlässigkeit-Robustheit und so weiter nicht auf einige der „Geschäftsmodell“ unterliegen Paradigma von wo wir bekommen einige der „Oh! das macht nichts“ auf Fragen, die eigentlich keine Rolle.

Als Schlussnote, Mess- und Regel umfassen mehr als 60% des tatsächlichen Marktes für versierte Programmierkenntnisse wie Sie beschreiben. Aus irgendeinem Grund hören wir meist über das Geschäftsmodell. Lassen Sie mich mit Ihnen teilen und innerhalb tidbit ich aus einer zuverlässigen Quelle. Von 10% bis 60% oder mehr tatsächliche Sicherheit und Eigentum Risiko kommt von Fahrzeug Problemen als von Einbrecher kommen, Diebstahl und diese Art der Sache. Sie werden nie Appelle für „90 d hören! Ays Mineralien auf Kreis Gewinnung mineralischer Rohstoffe faciltiy“für Verkehrstickets, in der Tat die meisten Menschen nicht einmal erkennen, dass Verkehrs Zitate sind Bustin. (N.A. - USA) Klasse 4 Vergehens und sind eigentlich klassifizierbar als solche

Klingt für mich wie Sie einen guten Schritt in Richtung auf ein paar gute Arbeit genommen haben, ...

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top