我曾经提出了一个关于C类型大小的问题,得到了一个很好的答案,但我意识到我的问题可能不太适合我的目的。
我之前是计算机工程师,后来成为了软件工程师,所以我喜欢计算机体系结构,并一直在思考如何制作虚拟机。我刚刚完成了一个有趣的项目,在Java上制作虚拟机,我感到非常自豪。但由于一些法律问题,我现在不能开源它,而且我目前有一些空闲时间。所以我想看看是否可以做一个在C上的虚拟机(速度更快),只是为了娱乐和教育。
问题是我不是一个C程序员,我最后一次写过一个非平凡的C问题已经超过10年了。我曾经是Pascal、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;
// 这是一个用于显示的辅助函数 void PrintData(Data *pData, DDouble *pResult) { printf("%5.2f + %5.2f = %5.2f\n", pData->A*1.0, pData->B, pResult->D); }
// 一些本地函数 void Plus(char* pParams, char* pResult) { Data *D = (Data *)pParams; // 不需要算术指针操作即可访问数据 DDouble *DD = (DDouble *)pResult; // 返回值同样如此 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). 数据大小 - 如果我在同一架构上使用相同的编译器编译VM和本地函数,则大小类型应该相同。
2). 字节序 - 同样适用于数据大小。
3). 内存对齐 - 这是一个问题,因为结构体中可能会添加填充字节,但在准备参数堆栈时很难进行同步(除了硬编码外没有其他方法可以知道如何添加填充)。
我的问题是:
1). 如果我知道类型的大小,是否有一种方法可以修改push和pull函数以确切地与结构填充同步? (修改以使编译器像数据大小和字节序问题一样处理它)。
2). 如果我将结构体打包为1(使用
#pragma pack(1)); (2.1)性能惩罚是否可接受?以及(2.2)程序稳定性是否存在风险?
3). 填充2、4或8个字节怎么样?哪个对于一般的32位或64位系统最好?
4). 您能指导我参考文献,例如针对x86上的GCC的确切填充算法吗?
5). 是否有更好的方法?
注意:跨平台不是我的最终目标,但我无法抗拒。只要不太丑陋,性能不是我的目标。所有这些都是为了乐趣和学习。
对于我的英语和非常长的帖子,我很抱歉。
提前感谢大家。