对结构进行操作并将结果分配给同一结构是否是一种不好的做法?为什么

Is it bad practice to operate on a structure and assign the result to the same structure? Why?

本文关键字:结构 一种 为什么 是否是 操作 结果 分配      更新时间:2023-10-16

我不记得看到过像这个假设片段这样的代码示例:

cpu->dev.bus->uevent = (cpu->dev.bus->uevent) >> 16; //or the equivalent using a macro  

其中,大型结构中的成员使用指针取消引用,对其进行操作,并将结果分配回结构的同一字段。

内核似乎是一个经常出现如此大型结构的地方,但我还没有看到它的例子,并对原因产生了兴趣。

这是否有性能原因,可能与遵循指针所需的时间有关?是不是好风格,如果是,首选方式是什么?

该语句在语法上没有错,但像这样编码更容易:

cpu->dev.bus->uevent >>= 16;

这更像是一个历史问题:内核大多是用C语言编写的(而不是C++),并且在最初的开发意图中(K&R时代)被认为是"高级汇编程序",其语句和表达式应该在C和ASM中具有字面对应关系。 在这种环境中,++i i+=1i=i+1是完全不同的东西,它们转换为完全不同的CPU指令

编译器优化,在

当时,不是那么先进和流行,所以遵循指针链两次的想法经常被避免,首先将结果目标地址存储在局部临时变量(很可能是register)中,然后做赋值。

(如int* p = &a->b->c->d; *p = a + *p;

或尝试使用像 a->b->c >>= 16;

对于现在的计算机(多核处理器,多级缓存和管道),在寄存器内部执行锥体的速度可以比内存访问快十倍,遵循三个指针比在内存中存储地址更快,从而恢复了"商业模式"的优先级。

然后,编译器优化可以自由更改生成的代码,使其适合大小或速度,具体取决于保留的内容更重要以及您使用的处理器类型。

所以 - 现在 - 无论你写++i还是i+=1i=i+1都无关紧要:编译器很可能会生成相同的代码,尝试只访问i一次。 并且遵循指针链两次很可能会被重写为等效于(cpu->dev.bus->uevent) >>= 16 >>=对应于 x86 衍生处理器中的单个机器指令。

也就是说("这并不重要"),代码风格也确实倾向于反映它最初编写的时代的风格和时尚(因为进一步的开发人员倾向于保持一致性)。

你的代码本身并不"坏",它只是在通常编写的地方看起来很"奇怪"。


只是为了让您了解什么是管道和预测。 考虑两个向量的比较:

bool equal(size_t n, int* a, int *b)
{
    for(size_t i=0; i<n; ++i)
       if(a[i]!=b[i]) return false;
    return true;
}

在这里,一旦我们发现不同的东西,我们就会说它们是不同的。

现在考虑一下:

bool equal(size_t n, int* a, int *b)
{
    register size_t c=0;
    for(register size_t i=0; i<n; ++i) 
       c+=(a[i]==b[i]);
    return c==n;
}

没有捷径可走,即使我们发现差异,也会继续循环和计数。但是从循环内部移除if后,如果n不是那么大(假设少于 20),这可能会快 4 或 5 倍!

优化的编译器甚至可以识别这种情况 - 证明没有不同的副作用 - 可以在第二个代码中返工第一个代码!

我认为这样的事情没有错,它看起来无害:

i = i + 42;

如果您经常访问数据项则可以考虑如下内容:

tSomething *cdb = cpu->dev.bus;
cdb->uevent = cdb->uevent >> 16;
// and many more accesses to cdb here

但是,即便如此,我还是倾向于将其留给优化器,无论如何,它往往比大多数人做得更好:-)

做这件事本身并没有什么错

cpu->dev.bus->uevent = (cpu->dev.bus->uevent) >> 16;

但根据uevent的类型,在像这样向右移动时需要小心,这样您就不会意外地将意想不到的位转移到您的值中。例如,如果它是一个 64 位值

uint64_t uevent = 0xDEADBEEF00000000;
uevent = uevent >> 16; // now uevent is 0x0000DEADBEEF0000;

如果您认为您移动了一个 32 位值,然后将新uevent传递给采用 64 位值的函数,那么您并没有像预期的那样传递0xBEEF0000。由于大小合适(64 位值作为 64 位参数传递),因此此处不会收到任何编译器警告(如果将 64 位值作为 32 位参数传递,则会收到此警告)。

同样有意思的是,上述操作虽然类似于

i = ++i;

这是未定义的行为(有关详细信息,请参阅 http://josephmansfield.uk/articles/c++-sequenced-before-graphs.html),仍然定义良好,因为右侧表达式中没有副作用。

相关文章: