Aditya Patel
Aditya Patel

Reputation: 11

Why pointer precedence in while loop is working differently?

*s++ evalutes right to left . while loop inside main is evaluting as right to left but while loop outside main is working as it has precedence left to rigth why???

int main(void) {
    char* s = "hello world";  
    char* p;
    str_cpy(p, s);
    printf("%s\n", p); // prints --> hello world 
    while(*s++) { // *s++ is working as *(s++) 
        printf("%c", *s); // prints --> ello world skips the 'h' character
    }
    return 0;
}

void str_cpy(char* p, char* s) {
     while(*p++ = *s++); //*s++ is working as (*s)++ 
}

Upvotes: -3

Views: 163

Answers (4)

Vlad from Moscow
Vlad from Moscow

Reputation: 311088

For starters the pointer p does not initialised and point to point to a vali object of an array type

char* p;

So the function call

str_cpy(p, s);

involes undefined behavior.

As for your question then this expression

*s++

contains two operators: the postfix increment operator ++ and the unary indirection operator *. Postfix operators have a higher precedence than unary operators. Thus the above expression is equivalent to the following expression

*(s++)

Now let's determine what is the value of the postfix increment operator ++. According to the C23 Standard (6.5.2.4 Postfix increment and decrement operators)

2 The result of the postfix ++ operator is the value of the operand. As a side effect, the value of the operand object is incremented (that is, the value 1 of the appropriate type is added to it).

So according to the quote the expression s++ yields the value of the pointer s before incrementing the pointer itself.

Knowing that it is easy to determine what happens in this while loop

while(*s++) { // *s++ is working as *(s++) 
    printf("%c", *s); // prints --> ello world skips the 'h' character
}

In the condition of the while loop there is checked the character of the current value of the pointer. As a side effect the pointer is incremented. So within the body of the while loop we have the pointer after its increment. As a result this statement

    printf("%c", *s); // prints --> ello world skips the 'h' character

outputs the character pointed to by the already incremented pointer within the condition of the while loop. That is within the condition of the while loop and inside the body of the while loop there are dereferenced different values of the pointer.

The while loop can be equivalently rewritten the following way

while(*s) {
    ++s;
    printf("%c", *s);
}

If you want to get a similar effect as in the while loop within the function str_cpy then you can write for example

while(*s) {
    printf("%c", *s++);
}

Upvotes: 1

John Bode
John Bode

Reputation: 123568

Postfix ++ has higher precedence than unary *, so *s++ is parsed as *(s++).

So you're dereferencing the result of s++, which is the current value of s, and then at some point s is incremented. It's logically equivalent to:

t0 <- s
t1 <- *t0
s <- s + 1

with the caveat that the last two operations can happen in any order, even simultaneously.

This works the same way in both while loops; in the first loop you're testing the result of *s and in the second you're testing the result of *p = *s.

The = operator doesn't force a sequence point and the side effect of ++ doesn't have to be applied immediately after evaluation, so p does not have to be incremented before *s++ is evaluated:

t0 <- p
t1 <- s
*t0 <- *t1
s <- s + 1
t <- t + 1

The last three operations can be evaluated in any order. C does not force left-to-right evaluation on assignment, so it's possible for *s++ to be evaluated before *p++.

Edit

And as Tom points out, you haven't set p to point to anything; if this code works at all it's by accident. Remember p doesn't store the string contents, it stores the address of the buffer where the string lives. Until you initialize it p's value is indeterminate and could point literally anywhere, which may or may not be writable and may or may not already be storing something important.

You'll either need to allocate memory for string at runtime and set p to point to it:

char *p = calloc(strlen(s) + 1, sizeof *p);

which will need to be deallocated when you're done:

free(p); // last thing before the return statement

or you'll need to declare it as an array large enough to hold the string:

char p[12] = {0};

or

char p[strlen(s) + 1];
memset(p, 0, sizeof p);

The second array is a variable length array whose size isn't known until runtime, so it can't have an initializer like the fixed-size example. When dealing with strings it's often a good idea to make sure your target buffer is zeroed out.

Upvotes: 2

xing
xing

Reputation: 2528

Add printf("%c", *s); to the body of the while loop in str_cpy and you will see that the behavior is the same.
As some programmer dude pointed out, p does not point to valid memory.

#include <stdio.h>
#include <stdlib.h>

void str_cpy(char* p, char* s) {
     while((*p++ = *s++)) {
        printf("%c", *s); // prints --> ello world skips the 'h' character
     }
}

int main(void) {
    char* s = "hello world";
    char* p = malloc ( 12);
    str_cpy(p, s);
    printf("\n\n%s\n\n", p); // prints --> hello world
    while(*s++) {
        printf("%c", *s); // prints --> ello world skips the 'h' character
    }
    free ( p);
    return 0;
}

Upvotes: 0

Some programmer dude
Some programmer dude

Reputation: 409422

Lets rewrite the loops with labels and goto...

The first loop will be something like this:

while_condition:
    {
        char temp = *s;
        s = s + 1;  // Here s is increased

        if (temp == '\0')
        {
            goto while_end;
        }
    }
while_body:
    {
        printf("%c", *s);
    }
    goto while_condition;
while_end:

As you can see the increment of s is part of the condition, so when you print *s you will never have the first character of the string.

Now we do the second loop:

while_condition:
    {
        char temp1 = *p;
        char temp2 = *s;

        *p = *s;

        p = p + 1;
        s = s + 1;

        if (temp2 == '\0')
        {
            goto while_end;
        }
    }
while_body:
    {
        // Empty
    }
    goto while_condition;
while_end:

This should hopefully make the different clear.

Upvotes: 1

Related Questions