我最近买了这本书,游戏引擎开发基础,第1卷:数学,其中的所有代码示例都是C ++。
在Vector3D的C ++实现中,作者创建了这样的索引器:
struct Vector3D
{
float x, y, z;
float& operator [](int i)
{
return ((&x)[i]);
}
}
所以,C#等效于:
struct Vector3D
{
float x, y, z;
public float this[int index]
{
get
{
fixed (float* x = &X)
{
return x[index];
}
}
}
}
这被允许吗?在C ++中,这是未定义的行为,因此我想知道在C#中这样做是否明智。这绝对是不安全的,但是,如果索引为0、1或2,行为是否会一致?
这里是完整的source code。
在最佳实践中,通过字段的内存地址访问字段吗?
简而言之:no。不,这不是最佳做法。没有任何性能上的好处,并且您失去了编译器强制的类型安全性。这样做可能会损害性能,因为编译器无法做出尽可能多的假设来优化已编译的程序。永远不要比编译器聪明。
在Vector3D的C ++实现中,作者创建了这样的索引器:
C ++代码正在做两件不同的事情:
正在返回float&
。
float&
是not指针或“原始”内存地址(内部可以由指针表示,但是当您拥有C ++中的引用时,可以将其转换为一些非常时髦的本机指令逻辑)一个好的编译器)。float&
(实际上是C#中的ref float
返回到数组元素或字段。假定结构的字段将按与数组元素相同的填充顺序进行布局。
sizeof()
包括必要的填充)。即使结构/类字段是统一类型,也不能将其保证。(&x)[i]
表达式没有真正的实用C#等效项-因为它只是错误struct Vector3D
缺少#pragma
指令来控制结构打包。因此,此C ++:
保证具有与此完全相同的内存中表示形式:class Foo { int x[3]; }
是否不是
class Bar { int x0; int x1; int x2; }
此在此质量检查中已说明:Layout in memory of a struct. struct of arrays and array of structs in C/C++
((虽然在典型的x86计算机上它们可能会“正常工作”-索引器也有可能正在读取和写入与数组元素不对应的部分内存,但是因为当使用原始指针时,C ++没有自动运行时边界检查功能,您将不知道代码是否会错误地覆盖内存,直到为时已晚(如果您使用[[lucky
,是操作系统或运行时),从而破坏了进程的内存”内存分配器might检测到错误并终止),这些假设与32位世界中令人毛骨悚然的C ++程序员所用的假设相同,例如,假设sizeof(int*) == sizeof(int)
-我们所有人都对此感到高兴我们在2000年代中期移至x64)现在,无论价格多少,C#does
都可以通过使用unsafe
属性为其用户提供有限数量的类型修饰,即使没有[FieldOffset]
修饰符也是如此(这是定义[ C#中的C0]以与本机API兼容:通过使用重叠的偏移量)-但这是以性能为代价的(有点直觉上相反:由于本机字对齐问题,较小且有效打包的结构较慢处理)。在union
上下文中,我相信
等效于C ++的C#就是这个(我可能是索引器错误,从我上次不得不使用unsafe
C#代码以来已经有5-6年了):unsafe
而且这么多层次都是错的:
struct Vector3D
{
[FieldOffset( sizeof(float) * 0 )]
private float x;
[FieldOffset( sizeof(float) * 1 )]
private float y;
[FieldOffset( sizeof(float) * 2 )]
private float z;
public unsafe float* this[Int32 i]
{
get
{
float* x0 = &this.x;
return &x0[i];
}
}
}
解释为按索引的字段数组的指针不会比仅使用开关快,这也将更安全。&this.x
和struct-packing实际上使程序FieldOffset
。float
[您也可以在C ++中执行相同的操作,并且与通过字段偏移量指针处理原始内存相比,编译器可以更好地优化直接命名字段访问,因此与原始的struct Vector3 { public float x; public float y; public float z; public ref float this[Int32 i] { get { switch( i ) { case 0: return ref this.x; case 1: return ref this.y; case 2: return ref this.z; default: throw new ArgumentOutOfRangeException( nameof(i) ); } } } }
类相比,仍然有可能获得更好的性能。] >更新
将固定缓冲区用作X,Y和Z的后备字段是否有意义?这将消除对switch语句的需要。此外,关于可变结构的主题,具有只读结构的新功能,是否真的有任何时间不应该将结构设为只读?和许多缺点(例如缺少边界检查,这对于内存安全性非常重要)。请注意,整数Locality of Reference直接用机器代码编译成非常快的本机跳转表,从而使其实际上是一种零成本的语言功能。在这种情况下,没有理由使用缓冲区(
Vector3D
或其他方式)。因为这是一个fixed
,所以将永远只有3个元素,因此它应该仅使用字段(如果使用堆分配的数组,则会失去Vector3
的好处。总结:
原始方法没有优点