首先,请了解该问题旨在询问 C 标准中指定的特性之外的特性。 C 标准没有对效率提出要求,因此任何关于效率的问题都必然是在询问 C 实现,而不是关于 C 标准。面试官并没有探究你对 C 本身的了解;他们正在探究您对现代硬件、编译器等的了解。
正如xvan 的回答中提到的,您可以使用*num = * (long *) buff;。 考虑到问题中隐含的一些假设,这很有效。为了使其可靠地工作:
long 不能有任何陷阱表示,否则我们必须知道被复制的数据不是陷阱表示。
long 必须是四个字节。
编译器必须容忍别名。也就是说,不能假设因为buff的元素是char,所以我们不会通过指向long的指针来访问它们。
buff 必须按照问题中的说明进行四字节对齐,否则目标硬件必须支持未对齐的负载。
这些特性在 C 实现中并不少见,尤其是在编译期间选择了相应的选项。这段代码的结果很可能是一个双指令序列,它将四个字节从内存加载到一个寄存器,并将四个字节从一个寄存器存储到内存。这就是我认为面试官在测试你的知识。
但是,这不是一个很好的解决方案。正如Ilja Everilä 在评论中指出的那样,您可以简单地写memcpy(&num, buff, sizeof num);。这是复制字节的正确 C 标准方法,一个好的编译器会优化它。例如,我刚刚在 macOS 10.12.6 上使用 Apple LLVM 8.1.0 编译了此源代码,并带有“-O3 -std=c11 -S”(请求优化的开关、使用 2011 C 标准和汇编代码输出):
#include <stdint.h>
#include <string.h>
void foo(uint32_t *L, char *A)
{
memcpy(L, A, sizeof *L);
}
生成的例程在通常的例程进入和退出代码之间包含这些指令:
movl (%rsi), %eax
movl %eax, (%rdi)
因此,编译器已将memcpy 调用优化为加载指令和存储指令。即使编译器不知道buff 的对齐方式可能是什么。它显然“相信”未对齐的加载和存储在目标架构上执行得相当好,因此它选择直接使用加载和存储指令实现memcpy,而不是显式调用库例程并循环复制四个单独的字节。
如果编译器没有像这样立即优化memcpy 调用,它可能需要一点帮助。例如,如果编译器不知道buff 是四字节对齐的,并且目标硬件不能很好地(或根本不)执行未对齐的四字节加载,那么编译器不会将这个memcpy 优化为加载-存储对。在这种情况下,一些编译器具有语言扩展,可以让您告诉他们指针具有比正常对齐更多的内容,例如 M.M. 提到的 GCC 的 __builtin_assume_aligned()。例如,Apple LLVM,我可以这样做:
typedef char AlignedBuffer[50] __attribute__((__aligned__(4)));
void foo(uint32_t *L, AlignedBuffer *A)
{
*L = * (long *) A;
}
typedef 告诉编译器AlignedBuffer 类型至少总是四字节对齐的。当然,这是对 C 语言的扩展,并非在所有编译器中都可用。 (另外,在执行此操作时,我必须确保使用支持通过指向其他类型的指针对事物进行别名的编译器选项。)
鉴于这个编译器已经知道如何优化这种情况,试图用指针转换来超越它是没有意义的。但是,当在其他情况下与其他编译器一起工作时,可能需要使用指针转换之类的东西来获得所需的性能。但是需要知道这是依赖于实现的,并且代码应该这样记录,以便其他人知道如果不解决这些问题就不能将其移植到其他 C 实现。
关于后续问题,可以写*num = * (long *) (buff + k);。这个后续问题的重点可能是探究您对硬件对齐要求的了解。在许多系统上,尝试从非四字节对齐的地址加载四字节数据会导致异常。因此,当k 不是四的倍数时,此赋值语句可能会在此类硬件上失败。 (此外,我们应该注意k 必须是所有要加载的字节都在buff 内,或者以其他方式已知可访问。)面试官可能希望您展示这些知识。
通常对于这样的面试问题,面试官想要的不一定只有一个正确答案。大多数情况下,他们希望看到您了解这些问题,对它们有一定的了解,并对解决这些问题的潜在方法有所了解。