Reputation: 502
I stumbled into this while solving an exercise:
void ft_striteri(char *s, void (*f)(unsigned int, char*))
{
unsigned int i;
if (!s || !f)
return ;
i = 0;
while (s[i++])
f(i, s + i);
}
Why doesn't the post increment in the while work while if i do this:
void ft_striteri(char *s, void (*f)(unsigned int, char*))
{
unsigned int i;
if (!s || !f)
return ;
i = -1;
while (s[++i])
f(i, s + i);
}
It works?
I'm new and still very confused to the whole pointer concept is there any nuance here that I don't know about?
Upvotes: 1
Views: 622
Reputation: 58500
The increment operators in C specify that, in addition to a value being computed by the expression, a side-effect occurs.
You might think that this is somehow special or peculiar to these operators, but in fact it isn't. The assignment operators are also this way.
For instance, ++(EXPR)
is equivalent to (EXPR) = (EXPR) + 1
, except that EXPR
is only evaluated once.
Both of these expressions calculate the result (EXPR) + 1
, and also have the side effect of storing that value back into (EXPR)
.
The side effect happens any time during the evaluation of the full expression which contains this subexpression.
Given:
while (s[i++])
f(i, s + i);
We have two full expressions here: the controlling expression of the while
loop, s[i++]
, is a full expression, and so is the f(i, s + i)
function call.
The side effects required by each full expression are settled before the next full expression is evaluated. The previous full expression sees the prior value, and the subsequent full expression sees the new value.
In other words, i++
here means:
Calculate the value of i
and also put i + 1
into i
.
Make sure that this i
update is done sometime during the evaluation of the full expression s[i++]
.
Therefore, the expression f(i, s + i)
is going to observe the new value of i
, not the previous value of i
that was used to calculate s[i]
. The function call will not be given the character that was tested to be non-zero, but the next character after it.
The important fact here is that side-effects are sequenced at the level of individual full expressions, not statements. Here, i++
does not mean "increment i
after each iteration of the entire while
loop, while i
keeps referring to the old value". If it worked that way, the code would work; but it doesn't.
Thus your revised statement fixed the consistency:
while (s[++i])
f(i, s + i);
because here ++i
means:
Calculate the value of i + 1
and also put that value into i
.
Same (2) as above.
Here, the controlling expression of while
tests s[i]
where i
is the new, incremented value of i
; and the function call f(i, s + i)
refers to the same i
. The controlling expression and function call are consistent: they work with the same character of the string.
You had to compensate for the preincrement by initializing i
to -1.
If you want to increment a variable after each iteration of the loop, and to do that near the top of the loop, then the for
construct is designed for exactly that:
// misconception: // similar idea, correct:
i = 0;
while (s[i++]) for (i = 0; s[i]; i++)
f(i, s + i); f(i, s + i);
The for
loop lets us have a kind of "postincrement" at the level of an entire statement: it has a place in the head syntax where we can specify the increment expressions that will evaluate after the entire body.
By the way, since i
is unsigned int
(which can also be specified as unsigned
, without int
), that type does not actually have a -1 value in its range. When we do this:
unsigned int x = -1; // initialize or assign a -1 value to unsigned
the negative value gets reduced to the smallest positive residue modulo UINT_MAX + 1
, and the resulting value is what is actually assigned.
The value -1 goes to UINT_MAX
. So you are really doing this:
i = UINT_MAX; // same meaning as i = -1.
this works because if i
is unsigned
and contains the maximum value UINT_MAX
, when we then increment i
, it goes to zero. This modulo or "wrapping" arithmetic is part of the definition of the unsigned
type; it is specified in the language standard. In the other direction, decrementing a zero-valued unsigned
likewise produces UINT_MAX
.
Also, as a matter of style, when referencing arrays, do not mix the ptr + index
and ptr[index]
notations. This is better:
// while the character isn't null, pass a pointer to that
// same character to f:
while (s[++i])
f(i, &s[i]); // address of the character
This &s[i]
means &*(s + i)
where the &*
("address of dereferenced pointer") operator combination "algebraically cancels out" leaving s + i
; it is no less efficient.
This recommendation is particularly relevant if the function f
is working with that one character s[i]
and not the entire s + i
substring of s
. The &array[index]
notation tends to be used (as a rule of thumb) when the emphasis is on a particular array element.
As a reader of C, you cannot trust that, of course: &array[index]
in someone else's program could be used to calculate a value which a function then uses to access other elements of the array, not only that one. As a writer of C, though, you can make your code "look like what it is doing", so there are fewer pitfalls for someone else.
Upvotes: 1
Reputation: 31559
The problem is the match between the comparison and the function call.
Consider the first iteration. In the first snippet it would be:
if(!s[0]) break;
f(1, s + 1);
In the second snippet it would be:
if(!s[0]) break;
f(0, s + 0);
Upvotes: 2
Reputation: 67476
If you do simple debugging you will see what the problem is.
void ft_striteri(char *s, void (*f)(unsigned int, char*))
{
unsigned int i;
i = 0;
while (s[i++])
{
printf("i = %d\n", i);
if(f) f(i, s + i);
}
}
void ft_striteri1(char *s, void (*f)(unsigned int, char*))
{
unsigned int i;
i = -1;
while (s[++i])
{
printf("i = %d\n", i);
if(f) f(i, s + i);
}
}
int main()
{
ft_striteri("Hello", NULL);
printf("\n");
ft_striteri1("Hello", NULL);
}
https://godbolt.org/z/cqb1aMGje Result:
i = 1
i = 2
i = 3
i = 4
i = 5
i = 0
i = 1
i = 2
i = 3
i = 4
Function with postincrement iterates from index 1 to 5 instead of 0 to 4.
But your both functions do not use the correct type for the indexes. It should be size_t
instead of int
.
I would personally write another way, having "positive" test checking if parameters are OK and have only one return point:
void ft_striteri(char *s, void (*f)(unsigned int, char*))
{
size_t i = 0;
if(s && f)
{
while (s[i])
{
f(i, s + i);
i++; // or ++i; - it makes no difference
}
}
}
Upvotes: 0
Reputation: 10435
I don't know about nuance, but here is an equivalent to your first:
void first(char *s, void (*f)(unsigned int, char*))
{
unsigned int i;
if (!s || !f)
return ;
i = 0;
while (s[i]) {
f(i, s + i + 1);
s = s + 1;
}
}
and second:
void second(char *s, void (*f)(unsigned int, char*))
{
unsigned int i;
if (!s || !f)
return ;
i = -1;
while (s[i+1]) {
f(i, s + i + 1);
i = i + 1;
}
}
Really, neither look right to me; I would think you would want:
void ft_striteri(char *s, void (*f)(unsigned int, char*))
{
unsigned int i;
if (!s || !f)
return ;
i = 0
while (s[i]) {
f(i, s + i);
i++
}
}
which, in idiomatic style might be:
void ft_striteri(char *s, void (*f)(unsigned int, char*))
{
int c;
if (!s || !f)
return ;
for (; *s; s++)
f(i, s);
}
Upvotes: -1