我问了一个关于C 型尺寸的问题,我得到了一个很好的答案,但我意识到我可能无法很好地表述这个问题以对我的目的有用。
在转到软件工程师之前,我的背景是计算机工程师,所以我喜欢计算机体系结构,并且一直在考虑制作 VM。我刚刚完成了一个在 Java 上制作 VM 的有趣项目,对此我感到非常自豪。但是有一些法律问题我现在不能开源它,我目前有一些空闲时间。所以我想看看我是否可以在 C 上制作另一个 VM(速度更快),只是为了好玩和教育。
问题是我不是 C 程序,上一次我写一个非琐碎的 C 问题是在 10 多年前。我是 Pascal、Delphi,现在是 Java 和 PHP 程序员。
我可以预见到许多障碍,我正在努力解决一个障碍,那就是访问现有的库(在 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 32bits GCC-C99)。如果这也适用于其他操作系统/架构,那就太好了。但是我们必须注意至少三个与内存相关的问题。
1)。数据大小 - 如果我在相同架构上使用相同的编译器编译 VM 和本机函数,大小类型应该是相同的。
2)。字节序 - 与数据大小相同。
3)。内存对齐 - 这是一个问题,因为填充字节可能会添加到结构中,但是在准备参数堆栈时很难同步它(除了硬编码之外,没有办法知道如何添加填充)。
我的问题是:
1)。如果我知道类型的大小,有没有办法修改推拉功能以与结构填充完全同步?(修改为让编译器像 Datasize 和 Endians 问题一样处理它)。
2)。如果我按一个(使用#pragma pack(1)
)打包结构;(2.1) 性能损失是否可以接受?(2.2) 项目稳定性是否会受到威胁?
3)。填充 2,4 或 8 怎么样?哪个适合一般的 32 位或 64 位系统?
4)。您能否指导我查看有关 x86 上 GCC 的精确填充算法的文档?
5)。有没有更好的方法?
注意:跨平台不是我的最终目标,但我无法抗拒。此外,只要性能不那么难看,性能就不是我的目标。所有这些都是为了娱乐和学习。
对不起我的英语和很长的帖子。
提前感谢大家。