当您可以直接将指针传递给 C 中的函数时,为什么还要使用 memcpy()?

Why use memcpy() when you can directly pass pointers to function in C?

这是BCrypt文件加密实用程序源代码的一部分。不变,除了我添加的一些评论。

uLong BFEncrypt(char **input, char *key, uLong sz, BCoptions *options) {
  uInt32 L, R;
  uLong i;
  BLOWFISH_CTX ctx;
  int j;
  unsigned char *myEndian = NULL;
  j = sizeof(uInt32);

  getEndian(&myEndian);

// makes space 2 bytes
  memmove(*input+2, *input, sz);

// add endian and compresssion option
  memcpy(*input, myEndian, 1);
  memcpy(*input+1, &options->compression, 1);

  sz += 2;    /* add room for endian and compress flags */ // total size increased

  Blowfish_Init (&ctx, key, MAXKEYBYTES); // initialize 

// encrypt the file
  for (i = 2; i < sz; i+=(j*2)) {   /* start just after tags */
    memcpy(&L, *input+i, j);// copy j bytes from input to L
    memcpy(&R, *input+i+j, j); // copy second j byte to R
    Blowfish_Encrypt(&ctx, &L, &R); // encrypt
    memcpy(*input+i, &L, j); // copy everything back
    memcpy(*input+i+j, &R, j);
  }

  if (options->compression == 1) {
    if ((*input = realloc(*input, sz + j + 1)) == NULL)
      memerror();

    memset(*input+sz, 0, j + 1);
    memcpy(*input+sz, &options->origsize, j);
    sz += j;  /* make room for the original size      */
  }

  free(myEndian);
  return(sz);
}

在循环中,我们首先将文件缓冲区逐字节复制到新变量,然后应用河豚加密。然后再次将字节复制到缓冲区。为什么我不能将字节直接传递给加密函数?为什么甚至需要 memcpy()

Why can't I pass bytes directly to the encrypting function?

有两条规则反对它,或者至少不支持它。

首先,如果 int 的对齐方式不正确,将指向 char 的指针转换为指向 int 的指针会产生未定义的行为,并且即使对齐正确,结果的值未完全定义。关于这个的规则在 C 2018 6.3.2.3 中,它涵盖了指针转换。

通常,int 等对象需要位于四个字节的倍数处。这是因为计算机内存和数据总线的组织方式;所涉及的各种“电线”被设置为以特定大小和排列的组来传输事物。当此类系统的编译器生成使用 int 对象的指令时,它会生成加载对齐字的指令。如果您采用未对齐的 char 指针并将其转换为指向 int 的指针,则当加载对齐字指令尝试使用未对齐的地址时,某些处理器会生成陷阱。其他处理器可能会忽略地址的低位并从不同的地址加载对齐字。

即使地址对齐,C标准也不保证将char *转换为int *的结果实际上指向与原来相同的地方。这是因为在一些系统中,现在大多是过时的,指向不同类型的指针以不同的方式表示。某些系统仅以多字节的字访问内存,因此,要实现 char *,编译器必须合成不同于硬件地址的地址,而对于 int *,编译器可能会直接使用硬件地址.

第二个规则是指定用于一种类型的内存,例如char的数组,不能随意用作另一种类型,例如int。这条规则在C 2018 6.5 7中,有特定的情况是允许的,比如任何类型,如intfloat,都可以作为char访问,但不能访问-反之亦然。此规则的目的是让通过 int *ifloat *f 的例程可以知道在这样的代码中:

for (int j = 0; j < 1024; ++j)
    f[j] += *i;

f[j] 总是访问 float 而从不访问 int,因此此循环的主体永远不会更改 *i 的值。这意味着编译器可以将代码优化为:

int t = *i;
for (int j = 0; j < 1024; ++j)
    f[j] += t;

省去了从内存中反复加载*i的工作,因为临时对象t可以保存在一个处理器寄存器中。 (最重要的是,编译器实际上可以使用 float t = *i;,保存从内存中重复加载 *i 和重复转换为 float 以进行添加。)

您可能会查看该动机,然后查看 Blowfish_Encrypt 并发现 BlowFish_Encrypt 从未从这种潜在的优化中受益,可能是因为它从未使用会受此规则影响的混合类型.然而,随着编译器在其转换中变得越来越先进和积极,编译器优化的复杂性变得越来越难以看到,因此很容易错过编译器从将一种类型别名化为另一种类型的规则中获得的一些优势。在任何情况下,因为规则存在,如果您违反它,您无法保证您的程序会正常工作。