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

Time:05-21

I am currently learning "pointers by following the book "The C Programming Language" by Brian and Dennis.

I failed to compile:

#include <stdio.h>

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

And the error messages are:

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)

After trying to figure out what causes the errors, I think it might be the "out-of-bounds".

So, I tried this to test my assumption:

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

Well, it still gave me the same error message above :(

Would it be the problem of the compiler?

I was using :

gcc -o [filename] [filename.c]

Could you help me out?

Thank you and have a lovely day!

CodePudding user response:

This is not an error but a warning.

Such a warning is meant to catch logical errors like this:

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

Where an assignment was used when an equality comparison should have been used.

In your case, the assignment is intentional. So you can do as the warning suggests and put an extra set of parenthesis around the assignment to let the compiler know your intent.

while ((*s   = *t  ))

CodePudding user response:

Regarding assignment inside conditions:

K&R was written in the 1970s and therefore follows 1970s (complete lack of) best practices. This way to implement strcpy from K&R actually became idiomatic C, even though it was later recognized as problematic for various reasons.

In the 1980s, programmers started to realize that using assignment inside if or loop conditions was bad practice. Partially because it introduced a side effect in the middle of a control statement, partially because programmers had a tendency to mix up = and == (especially Pascal programmers at the time, since Pascal uses := and =).

To counter this, some confused 1980s movement emerged, propagating the use of "the Yoda conditions", which is about programming with "backwards grammar" like the fictional Yoda character. That is if(0 == x) rather than if(x == 0).

However, this doesn't fix the more serious program design problem of mixing a control statement with one or several side effects. Also, people started to realize that compilers could easily warn against if(x = 0). So Turbo C from 1989 started to warn for "possible incorrect assignment", which was the end of the Yoda conditions.

All modern compilers have this warning too, but nowadays provide a "de facto standard" way to avoid the warning, namely to use double parenthesis: if((x = 0)). This means "I actually meant to do assignment here on purpose". So the quick & dirty fix to your problem is to change the code into:

while ((*s   = *t  ))

Other issues:

  • Avoid identifiers starting with a leading underscore since those are reserved for the standard library implementation.

  • Combining the operators with other operators in the same expression is generally considered much more dangerous than assignment inside conditions, because not only does it introduce a side effect, it introduces a potentially unsequenced side effect and it also makes the code hard to read. Having more than one side effect inside an expression is generally not a good idea.

Alternatives:

You could rewrite the code as (somewhat more readable)

void another_strcpy (char* s, char* t)
{
  for(*s = *t; *s != '\0'; s  , t  )
  {}
}

or perhaps as (very readable)

void another_strcpy (char* s, char* t)
{
  *s = *t; 
  while(*s != '\0')
  {
    s  ;
    t  ;
    *s = *t;
  }
}

Although as far as performance is concerned, all of these implementations (including the K&R one) are naive. Efficient copy algorithms, as seen inside production-quality standard libs, works on aligned chunks of data.

Also, ideally use const correctness and pointer aliasing optimizations:

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