为什么不能 while(*s++ = *t++);检查"out-of-bounds"后编译?

Why can't while(*s++ = *t++); be compiled after checking "out-of-bounds"?

我目前正在按照 Brian 和 Dennis 的《The C Programming Language》一书学习“指针”。

编译失败:

#include <stdio.h>

void _strcpy(char *s, char *t)
{
    while(*s++ = *t++)
        ;
}

错误信息是:

warning: using the result of an assignment as a condition without parentheses [-Wparentheses]
while(*s++ = *t++)
      ~~~~~^~~~~~
55.c:5:16: note: place parentheses around the assignment to silence this warning
    while(*s++ = *t++)
               ^
          (          )
55.c:5:16: note: use '==' to turn this assignment into an equality comparison
    while(*s++ = *t++)
               ^
               ==
1 warning generated.
Undefined symbols for architecture x86_64:
  "_main", referenced from:
     implicit entry/start for main executable
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

在尝试找出导致错误的原因后,我认为可能是“越界”。

所以,我试过这个来检验我的假设:

int main(void)
{
    char *t = "now is the time";
    char *s, *string = s;
    while(*s++ = *t++);
    printf("%s", string);
}

好吧,它仍然给我上面同样的错误信息:(

会不会是编译器的问题?

我正在使用:

gcc -o [filename] [filename.c]

你能帮帮我吗?

谢谢你,祝你有愉快的一天!

这不是错误,而是警告。

这样的警告是为了捕捉这样的逻辑错误:

int found=0;
while (found=0) {
    // do something
    if (abc) {
        found = 1;
    }
}

在本应使用相等比较时却使用了赋值。

在您的情况下,分配是有意的。所以你可以按照警告的建议去做,并在赋值周围加上一组额外的括号,让编译器知道你的意图。

while ((*s++ = *t++))

关于条件内赋值:

K&R 编写于 1970 年代,因此遵循 1970 年代(完全没有)最佳实践。这种从 K&R 实现 strcpy 的方式实际上成为了 C 语言的惯用方式,尽管后来由于各种原因被认为是有问题的。

在 1980 年代,程序员开始意识到在 if 或循环条件中使用赋值是不好的做法。部分原因是它在控制语句中间引入了副作用,部分原因是程序员倾向于混淆 ===(尤其是当时的 Pascal 程序员,因为 Pascal 使用 :==).

为了解决这个问题,出现了一些混乱的 1980 年代运动,宣传“尤达条件”的使用,即像虚构的尤达角色一样使用“向后语法”进行编程。那是 if(0 == x) 而不是 if(x == 0).

但是,这并没有解决更严重的程序设计问题,即控制语句与一个或多个副作用混合在一起。此外,人们开始意识到编译器可以很容易地警告 if(x = 0)。所以 Turbo C 从 1989 年开始警告“可能不正确的分配”,这是 Yoda 条件的结束。

所有现代编译器也有此警告,但现在提供了一种“事实上的标准”方法来避免警告,即使用双括号:if((x = 0))。这意味着“我实际上是故意在这里做作业”。因此,解决您的问题的快速而肮脏的方法是将代码更改为:

while ((*s++ = *t++))

其他问题:

  • 避免以前导下划线开头的标识符,因为它们是为标准库实现保留的。

  • ++ 运算符与同一表达式中的其他运算符组合通常被认为比条件内部赋值更危险,因为它不仅会引入副作用,还会引入潜在的未排序的副作用,它也使代码难以阅读。在一个表达式中有多个副作用通常不是一个好主意。

备选方案:

您可以将代码重写为(更具可读性)

void another_strcpy (char* s, char* t)
{
  for(*s = *t; *s != '[=11=]'; s++, t++)
  {}
}

或者可能是(非常可读)

void another_strcpy (char* s, char* t)
{
  *s = *t; 
  while(*s != '[=12=]')
  {
    s++;
    t++;
    *s = *t;
  }
}

尽管就性能而言,所有这些实现(包括 K&R 实现)都是幼稚的。正如 production-quality 标准库中所见,高效的复制算法适用于对齐的数据块。

此外,理想情况下使用 const 正确性和指针别名优化:

void another_strcpy (char* restrict s, const char* restrict t)