switch (my_array[n].type) {
case is_int:
// Do stuff for integer, using my_array[n].ival
break;
case is_float:
// Do stuff for float, using my_array[n].fval
break;
case is_char:
// Do stuff for char, using my_array[n].cvar
break;
default:
// Report an error, this shouldn't happen
}
void* tp; // tagged pointer
enum { is_int, is_double, is_char_p, is_char } type;
// ...
uintptr_t addr = (uintptr_t)tp & ~0x03; // clear the 2 low bits in the pointer
switch ((uintptr_t)tp & 0x03) // check the tag (2 low bits) for the type
{
case is_int: // data is int
printf("%d\n", *((int*)addr));
break;
case is_double: // data is double
printf("%f\n", *((double*)addr));
break;
case is_char_p: // data is char*
printf("%s\n", (char*)addr);
break;
case is_char: // data is char
printf("%c\n", *((char*)addr));
break;
}
如果你能确保数据是8字节对齐的(就像64位系统中的指针,或者long long和uint64_t...),那么你就有了一个额外的标记位。 这有一个缺点,如果数据没有存储在其他地方的变量中,则需要更多的内存。因此,如果数据的类型和范围有限,您可以将值直接存储在指针中。这种技术已经在32位版本的Chrome的V8引擎中使用,它会检查地址的最低有效位,看看这是一个 * 指向另一个对象的指针 *(如double,大整数,字符串或一些对象)还是一个 *31位有符号值 *(称为smi - small integer)。如果它是一个int,Chrome简单地做一个算术右移1位来获得值,否则指针被解引用。 在大多数当前的64位系统上,虚拟地址空间仍然比64位窄得多,因此最高有效位也可以用作标记。根据架构的不同,您可以使用不同的方式将它们用作标记。ARM、68k和许多其他的可以被配置为忽略最高位,允许你自由地使用它们,而不用担心segfault或任何事情。从上面链接的Wikipedia文章: 使用标记指针的一个重要例子是在ARM 64上的iOS 7上的Objective-C运行时,特别是在iPhone 5S上使用。在iOS 7中,虚拟地址是33位(字节对齐),因此字对齐地址只使用30位(3个最低有效位为0),剩下34位用于标记。C语言的类指针是单词对齐的,标记字段有很多用途,比如存储引用计数和对象是否有析构函数。 MacOS的早期版本使用称为句柄的标记地址来存储对数据对象的引用。地址的高位分别指示数据对象是否被锁定、可清除和/或源自资源文件。当MacOS在System 7中从24位寻址到32位时,这导致了兼容性问题。 https://en.wikipedia.org/wiki/Tagged_pointer#Examples 在x86_64 you can still use the high bits as tags with care上。当然,您不需要使用所有这16位,可以省略一些位以备将来证明 在Mozilla Firefox的早期版本中,他们也使用 * 小整数优化 *,如V8,3低位用于存储类型(int,string,object.但自从野猴之后,他们走上了另一条道路(Mozilla’s New JavaScript Value Representation,backup link)。该值现在始终存储在64位双精度变量中。当double是一个 * 归一化 * 时,它可以直接用于计算。但是,如果它的高16位都是1,这表示一个 *NaN *,低32位将存储地址(在32位计算机中)到值或直接值,其余16位将用于存储类型。这种技术被称为NaN-boxing或nun-boxing。它也用于64位WebKit的JavaScriptCore和Mozilla的SpiderMonkey,指针存储在低48位。如果你的主要数据类型是浮点型,这是最好的解决方案,并提供非常好的性能。 阅读更多关于上述技术:https://wingolog.org/archives/2011/05/18/value-representation-in-javascript-implementations
6条答案
按热度按时间7kqas0il1#
你可以让数组元素成为一个可区分的并集,也就是tagged union。
type
成员用于选择union
的哪个成员应该用于每个数组元素。所以如果你想在第一个元素中存储一个int
,你可以这样做:当你想访问数组中的一个元素时,你必须首先检查类型,然后使用联合体的相应成员。
switch
语句很有用:程序员需要确保
type
成员始终对应于union
中存储的最后一个值。xqkwcwgp2#
使用联合:
但是,您必须跟踪每个元素的类型。
wlsrxk513#
数组元素需要具有相同的大小,这就是为什么它是不可能的。你可以通过创建一个variant type来解决这个问题:
联合的元素的大小是最大元素的大小,4。
ojsjcaue4#
有一种不同风格的定义标签联盟(无论名称),IMO通过删除内部联盟使其更好地使用。这是X Window系统中用于事件之类的东西的样式。
Barmar的答案中的例子将内部联合命名为
val
。Sp.的答案中的示例使用匿名联合,以避免每次访问变体记录时都必须指定.val.
。不幸的是,C89或C99中没有“匿名”内部结构和联合。它是一个编译器扩展,因此本质上是不可移植的。IMO的一个更好的方法是颠倒整个定义。使每种数据类型都有自己的结构,并将标记(类型说明符)放入每个结构中。
然后将它们 Package 在一个顶级联盟中。
现在看来,我们似乎在重复自己,我们是。但是考虑到这个定义可能被隔离到单个文件中。但是我们已经消除了在你得到数据之前指定中间
.val.
的噪音。相反,它在最后,那里它不那么令人讨厌。:D个
另一个允许的是一种形式的继承。* 编辑:这部分不是标准的C,而是使用了GNU扩展。
上投下投
优化编译器可以忽略
.tag
初始化器,因为跟在 * 别名 * 后面的.int_
初始化器使用相同的数据区域。即使 * 我们 * 知道布局(!),它应该是好的。不不是使用“internal”标记代替(它覆盖了外部标记,就像我们想要的那样,但不会混淆编译器)。s4n0splo5#
你可以做一个
void *
数组,用一个单独的size_t.
数组,但是你失去了信息类型。如果你需要以某种方式保持信息类型,保持第三个int数组(其中int是一个枚举值),然后编写函数,根据
enum
值进行强制转换。tzxcd3kk6#
工会是标准的出路。但你也有其他的解决办法。其中之一是tagged pointer,它涉及在指针的 “free” 位中存储更多信息。
根据体系结构的不同,您可以使用低位或高位,但最安全和最便携的方式是利用对齐内存的优势使用未使用的低位。例如,在32位和64位系统中,指向
int
的指针必须是4的倍数(假设int
是32位类型),并且2个最低有效位必须为0,因此您可以使用它们来存储值的类型。当然,你需要在解引用指针之前清除标记位。例如,如果您的数据类型被限制为4种不同的类型,那么您可以像下面这样使用它如果你能确保数据是8字节对齐的(就像64位系统中的指针,或者
long long
和uint64_t
...),那么你就有了一个额外的标记位。这有一个缺点,如果数据没有存储在其他地方的变量中,则需要更多的内存。因此,如果数据的类型和范围有限,您可以将值直接存储在指针中。这种技术已经在32位版本的Chrome的V8引擎中使用,它会检查地址的最低有效位,看看这是一个 * 指向另一个对象的指针 *(如double,大整数,字符串或一些对象)还是一个 *31位有符号值 *(称为
smi
- small integer)。如果它是一个int
,Chrome简单地做一个算术右移1位来获得值,否则指针被解引用。在大多数当前的64位系统上,虚拟地址空间仍然比64位窄得多,因此最高有效位也可以用作标记。根据架构的不同,您可以使用不同的方式将它们用作标记。ARM、68k和许多其他的可以被配置为忽略最高位,允许你自由地使用它们,而不用担心segfault或任何事情。从上面链接的Wikipedia文章:
使用标记指针的一个重要例子是在ARM 64上的iOS 7上的Objective-C运行时,特别是在iPhone 5S上使用。在iOS 7中,虚拟地址是33位(字节对齐),因此字对齐地址只使用30位(3个最低有效位为0),剩下34位用于标记。C语言的类指针是单词对齐的,标记字段有很多用途,比如存储引用计数和对象是否有析构函数。
MacOS的早期版本使用称为句柄的标记地址来存储对数据对象的引用。地址的高位分别指示数据对象是否被锁定、可清除和/或源自资源文件。当MacOS在System 7中从24位寻址到32位时,这导致了兼容性问题。
https://en.wikipedia.org/wiki/Tagged_pointer#Examples
在x86_64 you can still use the high bits as tags with care上。当然,您不需要使用所有这16位,可以省略一些位以备将来证明
在Mozilla Firefox的早期版本中,他们也使用 * 小整数优化 *,如V8,3低位用于存储类型(int,string,object.但自从野猴之后,他们走上了另一条道路(Mozilla’s New JavaScript Value Representation,backup link)。该值现在始终存储在64位双精度变量中。当
double
是一个 * 归一化 * 时,它可以直接用于计算。但是,如果它的高16位都是1,这表示一个 *NaN *,低32位将存储地址(在32位计算机中)到值或直接值,其余16位将用于存储类型。这种技术被称为NaN-boxing或nun-boxing。它也用于64位WebKit的JavaScriptCore和Mozilla的SpiderMonkey,指针存储在低48位。如果你的主要数据类型是浮点型,这是最好的解决方案,并提供非常好的性能。阅读更多关于上述技术:https://wingolog.org/archives/2011/05/18/value-representation-in-javascript-implementations