我正在研究几年前编码的AES的旧实现,我想修改ShiftRows函数,该效率非常低。
暂时,我的ShiftRows基本上只交换连续数组元素(用一个字节表示)的值n次以实现循环排列。
我想知道是否有可能将我的element数组转换为单个变量以使用位移运算符进行置换? 这些行是4个无符号字符,因此每个4个字节。
在下面的代码中,似乎只有第一个字节(对应于“ a”)受位移位的影响。
char array[4][4] = {"abcd", "efgh", "ijkl", "mnop"};
int32_t somevar;
somevar = (int32_t)*array[0] >> 16;
自从我不练习C以来已经有很长时间了,所以我可能正在犯一些愚蠢的错误。
答案 0 :(得分:3)
首先,如果您的主要目标是快速AES实现,而不是实践C或快速但可移植的AES实现(即,可移植性是主要的,效率是次要的),那么您需要编写汇编语言语言(不是C语言),或至少对特定目标使用编译器功能,以便您编写准汇编代码。例如,Intel processors have AES-assist instructions, and GCC has built-in functions for them。
第二,如果要使用C语言执行此操作,则理想的主要工作是将所需的操作清楚地表达给编译器。通过这个,我的意思是您希望操作对编译器是透明的,以便其优化程序可以正常工作。使用各种技术重新解释数据(例如,从char
到int
)可能会阻碍编译器的优化能力。 (或者它们可能不,取决于编译器的质量和您编写的特定代码。)
如果您的目标是可移植代码,则可能最好只编写所需的字符运动(只需编写可移动数组元素的简单赋值语句)。好的编译器可以有效地转换它们,甚至在硬件支持的情况下,甚至可以将多个字节移动操作组合为单个字移动操作。
在编写“ fancy”代码以尝试进行优化时,重要的是要了解标准C的规则,正在使用的编译器的属性以及目标硬件。
例如,您有char array[4][4]
。这声明了没有特定对齐方式的数组。编译器可能将此数组以任何对齐方式放置在任何位置,例如,不一定要对齐四个字节的倍数。如果然后使用指向此数组第一行的指针并将其转换为指向int
的指针,则在某些处理器上加载int
的指令可能会失败,因为它们需要{{1} }对象要对齐为四个字节的倍数。在其他处理器上,负载可能会起作用,但比对齐的负载要慢。
一个解决方案是不声明裸数组也不转换指针。相反,您将声明一个并集,其中一个成员可能是四个int
的数组,另一个可能是四个四个uint32_t
的数组的数组。联合中uint8_t
数组的存在将迫使编译器使其适合于硬件。此外,C中允许通过联合来重新解释数据,而通过转换后的指针重新解释数据不是正确的C代码。 (即使满足对齐要求,通过指针重新解释数据也通常会违反别名规则。)
另一方面,在处理位时,通常最好使用无符号类型,就像在加密代码中一样。最好使用uint32_t
和char
,而不是int32_t
和uint8_t
。
关于您的特定代码:
uint32_t
somevar = (int32_t)*array[0] >> 16;
是array[0]
的第一行。根据C的规则,它会自动转换为指向其第一个元素的指针,因此它变为array
。然后&array[0][0]
是*array[0]
,它是*&array[0][0]
,它是数组第一行中的第一个array[0][0]
。因此,到目前为止的表达式只是第一个char
的值。然后,强制转换char
将表达式的类型转换为(int32_t)
。这不会更改值,因此结果只是第一行中第一个int32_t
的值。
您可能会想到的是char
或* (uint32_t *) &array[0]
。它们采用第一行的地址(前一个表达式)或第一行的第一个元素的地址(后一个表达式)(表示相同的位置,但类型不同)并将其转换为指向{ {1}}。然后* (uint32_t) array[0]
打算在该地址获取uint32_t
。这违反了C规则,应该避免。
您可以使用:
*
然后,您可以使用uint32_t
访问单个字节,或者使用union
{
uint32_t words[4];
uint8_t bytes[4][4];
} block;
访问四个字节的单词。这是否一个好主意取决于上下文和目标。