c - 高效地将一种整数类型的数据转换为另一种具有相同表示形式的数据

标签 c strict-aliasing

大多数微型计算机 C 编译器都有两个大小和表示相同的有符号整数类型,以及两个这样的无符号整数类型。如果int是16位,它的表示一般会匹配short ;如果long是64位的,一般会匹配long long ;否则,intlong通常会有匹配的 32 位表示。

如果,在平台上 long , long long , 和 int64_t具有相同的表示,需要按顺序将缓冲区传递给三个 API 函数(假设 API 在其他人的控制下并使用指示的类型;如果函数可以很容易地更改,则可以简单地将它们更改为使用相同的整个类型)。

void fill_array(long *dat, int size);
void munge_array(int64_t *dat, int size);
void output_array(long long *dat, int size);

是否有任何有效的符合标准的方式允许所有这三个 功能使用相同的缓冲区,而不需要所有的数据 在函数调用之间被复制?我怀疑 C 的别名规则的作者是否打算让这样的事情变得困难,但“现代”编译器很流行假设没有通过 long* 编写的内容。将通过 long long* 阅读,即使这些类型具有相同的表示形式。此外,同时 int64_t通常与 long 相同或 long long , 实现不一致。

在不通过函数调用积极追求基于类型的别名的编译器上,可以简单地将指针转换为正确的类型,可能包括静态断言以确保所有类型具有相同的大小。问题是,如果像 gcc 这样的编译器在展开函数调用后,发现某些存储被写为 long。后来读作 long , 没有任何 long 类型的中间写入, 它可能会用写成 long 类型的值替换后面读取的值,即使中间有 long long 类型的写入.

完全禁用基于类型的别名当然是一种方法 这样的代码工作。任何体面的编译器都应该允许这样做,并且它会避免很多 其他可能的陷阱。不过,似乎应该有一个标准—— 定义的方式来有效地执行这样的任务。有吗?

最佳答案

is there any efficient standard-compliant way of allowing all three functions to use the same buffer without requiring that all of the data be copied between function calls? I doubt the authors of C's aliasing rules intended that such a thing should be difficult, but it is fashionable for "modern" compilers to assume that nothing written via long* will be read via long long*, even when those types have the same representation.

C 指定 longlong long 是不同的类型,即使它们具有相同的表示形式。无论表示如何,它们都不是标准定义意义上的“兼容类型”。因此,严格的别名规则 (C2011 6.5/7) 适用:具有有效类型 long 的对象不得通过 long long 类型的左值访问其存储值,并且反之亦然。因此,无论缓冲区的有效类型是什么,如果程序访问 long 类型和 long long 类型的元素,您的程序都会表现出未定义的行为。

虽然我同意该标准的作者并不打算让您描述的内容变得困难,但他们也没有特别的意图让它变得简单。他们最关心的是定义程序行为的方式,即尽可能在允许实现的所有自由度方面保持不变,在这些自由度中,long long 可以有不同的表示比。因此,任何依赖于它们具有相同表示的程序都不能严格遵守,无论这种依赖的性质或上下文如何。

Still, it seems like there should be a Standard- defined way to perform such a task efficiently. Is there?

没有。缓冲区的有效类型是其声明的类型(如果它有一个),否则由设置其存储值的方式定义。在后一种情况下,如果写入不同的值,情况可能会发生变化,但任何给定值都只有一种有效类型。无论其有效类型是什么,严格的别名规则都不允许通过 long 类型和 long long 类型的左值访问该值。期间。

Disabling type-based aliasing altogether is of course one approach to making such code work. Any decent compiler should allow that, and it will avoid many other possible pitfalls.

事实上,那种或其他一些特定于实现的方法(可能包括 It Just Works)是您在不复制的情况下在您提供的三个函数之间共享相同数据的唯一选择。

更新:

在某些受限情况下,可能会有更基于标准的解决方案。例如,对于您指定的特定 API 函数,您可以执行如下操作:

union buffer {
    long       l[BUFFER_SIZE];
    long long ll[BUFFER_SIZE];
    int64_t  i64[BUFFER_SIZE]; 
} my_buffer;

fill_array(my_buffer.l, BUFFER_SIZE);
munge_array(my_buffer.i64, BUFFER_SIZE);
output_array(my_buffer.ll, BUFFER_SIZE);

(感谢@Riley 给了我这个想法,尽管它与他的有点不同。)

当然,如果您的 API 动态分配缓冲区本身,那是行不通的。还要注意,

  • 使用该方法的程序可能符合标准,但如果它假定 long 的相同表示,long long , 和 int64_t 那么它仍然不严格符合标准定义该术语。

  • 标准在这一点上有点不一致。它关于通过 union 允许类型双关的评论在脚注中,脚注是非规范的。该脚注中描述的重新解释似乎与规范性的第 6.5/7 段相矛盾。我更愿意让我的关键任务代码远离此类不确定性,因为即使我们得出结论认为这种方法应该有效,这种不确定性也恰恰提供了编译器错误喜欢寄宿的那种缝隙。

    <
  • 该领域相当知名的人物once had this to say关于这个问题:

Unions are not useful [for aliasing], regardless of what silly language lawyers say, since they are not a generic method. Unions only work for trivial and largely uninteresting cases, and it doesn't matter what C99 says about the issue, since that nasty thing called "real life" interferes.

关于c - 高效地将一种整数类型的数据转换为另一种具有相同表示形式的数据,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39396546/

相关文章:

编写就地浮点型提升循环的正确方法?

c - 我是否正确应用了严格别名规则?

c++ - 严格违反别名规则

C 内存分配器和严格的别名

c - 如何并行化这个C程序?

c - 如何检查 int var 是否包含特定数字

c - 为目录中的所有文件添加头文件

c - 在程序集中正确存储 8 字节值

循环FOR生成两个数字序列