NULL + int的结果是什么?

我已经看到在OpenGL VBO实现中使用下面的macros:

#define BUFFER_OFFSET(i) ((char *)NULL + (i)) //... glNormalPointer(GL_FLOAT, 32, BUFFER_OFFSET(x)); 

你能否提供一些关于这个macros如何工作的细节? 它可以被replace为一个function? 更确切地说,增加一个NULL指针的结果是什么?

让我们回顾一下OpenGL的肮脏的历史。 曾几何时,有OpenGL 1.0。 你用glBeginglEnd做绘图,就是这样。 如果你想快速绘制,你把东西卡在显示列表中。

然后,有人有一个聪明的想法,只能采取对象的数组渲染。 因此诞生了OpenGL 1.1,为我们带来了像glVertexPointer这样的function。 您可能会注意到,此function以单词“指针”结束。 这是因为它需要指向实际内存的指针,当调用一个glDraw*函数套件时将访问这些指针。

快进几年。 现在,人们希望将顶点数据放在GPU内存中,但他们不想信任显示列表。 那些太隐蔽了,没有办法知道你是否会得到好的performance。 input缓冲区对象。

然而,由于ARB有一个绝对的策略,即尽可能地向后兼容(不pipeAPI看起来多么愚蠢),他们决定实现这个的最好方法就是再次使用相同的函数。 直到现在,还有一个全局开关,将glVertexPointer的行为从“将指针”改为“从缓冲区对象中取出一个字节偏移量”。 该开关是否是一个缓冲区对象绑定到GL_ARRAY_BUFFER

当然,就C / C ++而言,该函数仍然需要一个指针 。 而C / C ++的规则不允许你传递一个整数作为指针。 不是没有演员 这就是为什么像BUFFER_OBJECT这样的macros存在的原因。 这是将整数字节偏移量转换为指针的一种方法。

(char *)NULL部分简单地采用NULL指针(通常是void* )并将其转换为char*+ i只是在char*上进行指针运算。 由于NULL通常是一个零值,因此将i加到i将会增加字节偏移量,从而生成一个指针,其值就是您传入的字节偏移量。

当然,C ++规范将BUFFER_OBJECT的结果列为未定义的行为 。 通过使用它,你真的依靠编译器来做一些合理的事情。 毕竟, NULL不一定是零; 所有规范说的是它是一个实现定义的空指针常量。 它根本不必具有零值。 在大多数真实的系统上,它会的。 但它并不是必须的。

这就是为什么我只使用一个演员。

 glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0, (void*)48); 

这是不确定的行为。 但是比键入“BUFFER_OFFSET”还短。 GCC和Visual Studio似乎觉得合理。 而且它不依赖于NULLmacros的值。

就个人而言,如果我是更多的C ++迂腐,我会使用一个reinterpret_cast<void*>就可以了。 但我不是。

你可以写一个glVertexAttribBuffer接受一个偏移量而不是一个指针? 绝对。 但这并不是什么大问题。 只是做一个演员。

 #define BUFFER_OFFSET(i) ((char *)NULL + (i)) 

从技术上来说,这个操作的结果是不确定的 ,macros观实际上是错误的。 让我解释:

C定义了(而C ++遵循它),指针可以被转换为整数,即types为uintptr_t ,并且如果以这种方式获得的整数被转换回来的原始指针types,则会产生原始指针。

然后是指针算术,这意味着如果我有两个指针指向所以相同的对象,我可以把它们的差异,产生一个整数(typesptrdiff_t ),该整数加上或减去任何一个原始指针,将产量另一个。 还定义了,通过给一个指针加1,就得到了指向索引对象下一个元素的指针。 另外,两个uintptr_t的差值除以同一对象的指针的sizeof(type pointed to)必须等于指针本身被减去的指针。 最后但并非最不重要的, uintptr_t值可能是任何东西。 它们也可以是不透明的手柄。 他们不需要成为地址(虽然大多数实现这样做,因为它是有道理的)。

现在我们可以看看臭名昭着的空指针。 C定义了types为uintptr_u 0作为无效指针被铸造的指针。 请注意,源代码中始终为0 。 在后端,在编译的程序中,用于实际向机器表示的二进制值可能是完全不同的东西! 通常不是,但可能是。 C ++是相同的,但是C ++不允许比C更多的隐式转换,所以必须将0明确地转换为void* 。 另外,由于空指针并没有引用一个对象,因此没有解引用的大小指针,空指针的算术未定义 。 引用没有对象的空指针也意味着,没有明确地将它转换为types指针的定义。

所以如果这一切都没有定义,为什么这个macros毕竟工作? 因为大多数的实现(意味着编译器)是非常容易受骗的,而编译器的编码器却是最懒的。 大多数实现中的指针的整数值只是在后端的指针本身的值。 因此,空指针实际上是0.虽然空指针上的指针运算没有被检查,但是大多数编译器会默默地接受它,如果指针得到了某种types的指派,即使它没有意义。 如果你想这样说, char是C的“单位大小”types。 所以在cast上的指针运算就像在后端的地址上的artihmetic一样。

长话短说,尝试使用指针魔法来达到预期的结果是C语言方面的偏移是没有意义的,但这并不是那种方式。

让我们退后一步,记住,我们实际上想要做的是:最初的问题是, gl…Pointer函数将一个指针作为它们的数据参数,但是对于顶点缓冲区对象,我们实际上想要指定一个基于字节抵消了我们的数据,这是一个数字。 对于C编译器来说,函数需要一个指针(我们学到的是一个不透明的东西)。 正确的解决办法就是引入新的function,特别是与维也纳国际组织(比如说,国际货币基金组织)的使用( gl…Offset – 我认为我将要引进他们)。 相反,OpenGL定义的是编译器如何工作的一个漏洞。 指针和它们的整数等价物被大多数编译器实现为相同的二进制表示。 所以我们必须做的,它使编译器调用这些gl…Pointer函数与我们的数字,而不是一个指针。

所以在技术上我们唯一需要做的就是告诉编译器:“是的,我知道你认为这个variables是一个整数,你是对的,而且这个函数glVertexPointer只需要一个void*作为它的数据参数。这个整数是从void*产生的,通过将其转换为(void*) ,然后保持大拇指,编译器实际上是如此愚蠢的传递整数值,因为它是glVertexPointer

所以这一切都归结为以某种方式规避了旧的function签名。 投射指针是恕我直言的肮脏的方法。 我会做一点不同的:我会乱七八糟的function签名:

 typedef void (*TFPTR_VertexOffset)(GLint, GLenum, GLsizei, uintptr_t); TFPTR_VertexOffset myglVertexOffset = (TFPTR_VertexOffset)glVertexPointer; 

现在你可以使用myglVertexOffset而不做任何愚蠢的转换,并且offset参数将被传递给函数,而没有任何危险,编译器可能会混淆它。 这也是我在程序中使用的方法。

这不是“NULL + int”,这是一个“NULL转换为指向char的types的指针”,然后通过i递增该指针。

是的,这可以用一个函数来代替 – 但是如果你不知道它做了什么,那么你为什么要关心它呢? 首先了解它做了什么, 然后考虑它是否会更好。

根据上下文,openGL顶点属性数据通过相同的函数(glVertexAttribPointer)分配为内存中的指针或位于顶点缓冲区对象内的偏移量。

BUFFER_OFFSET()macros似乎将一个整数字节偏移量转换为一个指针,只是为了让编译器安全地将它作为一个指针parameter passing。 “(char *)NULL + i”表示通过指针运算的转换; 结果应该是相同的位模式,假设sizeof(char)== 1,没有它,这个macros将失败。

也可以通过简单的重新铸造来实现,但是这个macros可以使得它在风格上更加清晰, 它也将是一个方便的地方,以捕获32/64位安全/ futureproofing溢出

 struct MyVertex { float pos[3]; u8 color[4]; } // general purpose Macro to find the byte offset of a structure member as an 'int' #define OFFSET(TYPE, MEMBER) ( (int)&((TYPE*)0)->MEMBER) // assuming a VBO holding an array of 'MyVertex', // specify that color data is held at an offset 12 bytes from the VBO start, for every vertex. glVertexAttribPointer( colorIndex,4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(MyVertex), (GLvoid*) OFFSET(MyVertex, color) // recast offset as pointer );