Cタイプのサイズについて質問したところ、かなり良い答えが得られましたが、自分の目的に役立つように質問をうまく定式化できない可能性があることに気付きました。
私の経歴は、ソフトウェアエンジニアに移る前はコンピュータエンジニアでした。そのため、コンピュータアーキテクチャが好きで、常にVMの作成を考えています。私は、JavaでVMを作成する興味深いプロジェクトを終えたところです。これは、私が非常に誇りに思っていることです。しかし、現在オープンソース化できない法的な問題がいくつかあり、現在は自由な時間があります。ですから、楽しみと教育のためだけに、Cで別のVMを(より高速に)作成できるかどうかを確認したいと思います。
問題は、私がトリビア以外のCの問題を最後に書いたのは、10年以上前のCプログラムではないということです。私はPascal、Delphi、そして今ではJavaとPHPのプログラマーでした。
私が予見できる多くの障害があり、私は1つに取り組もうとしており、それは既存のライブラリにアクセスしています(Javaでは、リフレクションがこの問題を解決します)。
データのバッファ(スタックと同様)を用意することで、これを解決する予定です。VMのクライアントは、ネイティブ関数へのポインターを指定する前に、これらのスタックにデータを配置するようにプログラムできます。
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;
}
ネイティブ関数のプッシュ、プル、および呼び出しは、バイトコード(VMが後で作成される方法)によってトリガーできます。
完全を期すために(マシンで試すことができるように)、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));
}
ネイティブ関数側では、テスト目的で次のものを作成しました。
// 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);
}
実行すると、上記のコードは次を返します。
10.00 + 15.30 = 25.30
Result: 25.30
Pointer: 0
これは私のマシン(Linux x86 32ビットGCC-C99)でうまく機能します。これが他のOS/アーキテクチャでも機能する場合は非常に便利です。しかし、私たちが注意しなければならない少なくとも3つのメモリ関連の問題があります。
1)。データサイズ-同じアーキテクチャで同じコンパイラを使用してVM関数とネイティブ関数の両方をコンパイルする場合、サイズタイプは同じである必要があるようです。
2)。エンディアン-データサイズと同じ。
3)。メモリアライメント-これは構造体にパディングバイトが追加される可能性があるため問題ですが、パラメータスタックを準備するときに同期するのは困難です(ハードコーディング以外にパディングがどのように追加されるかを知る方法はありません)。
私の質問は次のとおりです。
1)。タイプのサイズがわかっている場合、構造体のパディングと正確に同期するようにプッシュおよびプル関数を変更する方法はありますか?(コンパイラがデータサイズやエンディアンの問題のように処理できるように変更します)。
2)。構造を1つずつパックすると(を使用#pragma pack(1)
); (2.1)パフォーマンスペナルティは許容されますか?(2.2)プログラムの安定性は危険にさらされますか?
3)。2、4、または8でパディングするのはどうですか?一般的な32ビットまたは64ビットシステムにはどちらが適していますか?
4)。x86上のGCCの場合など、正確なパディングアルゴリズムのドキュメントを教えていただけますか?
5)。より良い方法はありますか?
注:クロスプラットフォームは私の究極の目標ではありませんが、抵抗することはできません。また、それほど醜くないので、パフォーマンスは私の目標ではありません。これらはすべて楽しみと学習のためのものです。
私の英語と非常に長い投稿でごめんなさい。
よろしくお願いします。