Я задал вопрос о размерах C-типа, которые я получаю довольно хороший ответ, но я понял, что могу не очень хорошо сформулировать вопрос, чтобы быть полезным для моей цели.
Я работал инженером-программистом до того, как перешел на инженера-программиста, поэтому мне нравятся компьютерные архитектуры, и я всегда думаю о создании ВМ. Я только что закончил интересный проект по созданию виртуальной машины на Java, которым очень горжусь. Но есть некоторые юридические проблемы. Я не могу открыть его сейчас, и у меня сейчас есть свободное время. Поэтому я хочу посмотреть, смогу ли я создать еще одну виртуальную машину на C (с большей скоростью) просто для развлечения и обучения.
Дело в том, что я не являюсь программой на языке C, когда в последний раз я писал нетривиальную проблему на языке C более 10 лет назад. Я был программистом на Паскале, Delphi, а теперь на Java и PHP.
Есть ряд препятствий, которые я могу предвидеть, и я пытаюсь решить одно, а именно доступ к существующей библиотеке (в Java отражение решает эту проблему).
Я планирую решить эту проблему с помощью буфера данных (похожего на стек). Клиент моей виртуальной машины может запрограммировать помещать данные в этот стек перед тем, как передать мне указатель на собственную функцию.
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;
}
Выталкивание, вытягивание и вызов собственной функции может быть инициировано байтовым кодом (именно так позже будет создана виртуальная машина).
Для полноты (чтобы вы могли опробовать его на своем компьютере) вот код для 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). Было бы очень хорошо, если бы это работало и на другой ОС / архитектуре. Но мы должны знать, ПО МЕНЮ, три проблемы, связанные с памятью.
1). Размер данных. Похоже, что если я компилирую как виртуальную машину, так и собственные функции, используя один и тот же компилятор на одной архитектуре, типы размеров должны быть одинаковыми.
2). Порядок байтов - то же самое с размером данных.
3). Выравнивание памяти - это проблема, поскольку байты заполнения могут быть добавлены в структуру, но их трудно синхронизировать при подготовке стека параметров как (нет способа узнать, как добавляется заполнение, за исключением жесткого кодирования).
Мои вопросы:
1). Если я знаю размер типов, есть ли способ изменить функцию push и pull для точной синхронизации с заполнением структуры? (измените, чтобы компилятор позаботился об этом, как о задачах Datasize и Endians).
2). Если я упаковываю структуру по одному (используя #pragma pack(1)
); (2.1) Будет ли приемлемо снижение производительности? и (2.2) Будет ли под угрозой стабильность программы?
3). Как насчет заполнения 2,4 или 8? Что должно быть хорошо для общей 32- или 64-битной системы?
4). Можете ли вы дать мне документацию по точному алгоритму заполнения, например, для GCC на x86?
5). Есть способ лучше?
ПРИМЕЧАНИЕ: кроссплатформенность - это не моя конечная цель, но я не могу устоять. Кроме того, производительность не является моей целью, раз уж она не такая уж уродливая. Все это для развлечения и обучения.
Извините за мой английский и очень длинный пост.
Заранее всем спасибо.