是否可以对多个连续的数组元素进行按位运算?

Is it possible to do bitwise operation on multiple successive array elements?

我正在研究几年前编写的 AES 的旧实现,我想修改效率非常低的 ShiftRows 函数。

暂时我的 ShiftRows 基本上只是交换连续数组元素(由一个字节表示)的值 n 次以实现循环排列。

我想知道是否可以使用我的元素数组并将其转换为单个变量来使用位移运算符进行置换? 这些行是 4 个无符号字符,所以每行 4 个字节。

在下面的代码中,只有第一个字节(对应于 'a')似乎受到位移的影响。

char array[4][4] = {"abcd", "efgh", "ijkl", "mnop"};

int32_t somevar;

somevar = (int32_t)*array[0] >> 16;

好久没练C了,估计犯了一些蠢错误

首先,如果您的主要目标是快速实现 AES,而不是练习 C 或快速但可移植的 AES 实现(也就是说,可移植性是首要的,效率是次要的),那么您需要编写使用汇编语言,而不是 C,或者至少为特定目标使用编译器功能,让您编写接近汇编的代码。例如,Intel processors have AES-assist instructions, and GCC has built-in functions for them.

其次,如果您打算在 C 中执行此操作,理想情况下,您的主要工作是向编译器清楚地表达所需的操作。通过这个,我的意思是您希望操作对编译器透明,以便其优化器可以工作。使用各种技术重新解释数据(例如,从 charint)会阻碍编译器的优化能力。 (或者他们可能不会,这取决于编译器质量和您编写的特定代码。)

如果您的目标是可移植代码,最好只编写您想要的角色动作(只需编写移动数组元素的简单赋值语句)。好的编译器可以有效地翻译这些,如果硬件支持的话,甚至可以将多个字节移动操作组合成单个字移动操作。

当您编写“花哨的”代码以尝试优化时,了解标准 C 的规则、您正在使用的编译器的属性以及您的目标硬件是很重要的。

例如,您有char array[4][4]。这声明了一个没有特定对齐方式的数组。编译器可能会将此数组放置在任何位置,并采用任何对齐方式——例如,它不一定与四个字节的倍数对齐。如果您随后获取指向此数组第一行的指针并将其转换为指向 int 的指针,则加载 int 的指令可能在某些处理器上失败,因为它们需要 int ] 对象对齐到四个字节的倍数。在其他处理器上,加载可能会工作,但比对齐加载慢。

对此的一种解决方案是不声明裸数组并且不转换指针。相反,您可以声明一个联合,其中一个成员可能是一个包含四个 uint32_t 的数组,另一个成员可能是一个包含四个数组的数组,每个数组包含四个 uint8_tuint32_t 数组在联合中的存在将迫使编译器将其适当地对齐以适应硬件。此外,在 C 中允许通过联合重新解释数据,而通过转换指针重新解释数据不是正确的 C 代码。 (即使满足对齐要求,通过指针重新解释数据通常会违反别名规则。)

另一方面,当像在密码代码中那样处理位时,通常最好使用无符号类型。而不是 charint32_t,你可能会更好 uint8_tuint32_t.

关于您的具体代码:

somevar = (int32_t)*array[0] >> 16;

array[0]array的第一行。根据 C 的规则,它会自动转换为指向其第一个元素的指针,因此它变为 &array[0][0]。那么*array[0]就是*&array[0][0],也就是array[0][0],也就是数组第一行的第一个char。所以到目前为止的表达式只是第一个 char 的值。然后强制转换 (int32_t) 将表达式的类型转换为 int32_t。这不会更改值,因此结果只是第一行中第一个 char 的值。

您可能想到的是 * (uint32_t *) &array[0]* (uint32_t) array[0]。它们采用第一行的地址(前一个表达式)或第一行的第一个元素的地址(后一个表达式)(它们表示相同的位置但类型不同)并将其转换为指向 uint32_t。然后 * 旨在获取该地址的 uint32_t 。这违反了 C 规则,应该避免。

相反,您可以使用:

union
{
    uint32_t words[4];
    uint8_t  bytes[4][4];
} block;

然后您可以使用 block.bytes[i][j] 访问单个字节或使用 block.words[i] 访问四个字节的单词。这是否是一个好主意取决于背景和目标。