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