[TinyCC] sizeof of element of struct returned by function

M

Maciej Labanowicz

Hello,

Is following code correct (ANSI C89) ?
----
01: #include <stdio.h>
02: #include <stdlib.h>
03: struct { char array1 [100]; } * fun1(int x1);
04: struct { char array2 [200]; } fun2(int x2);
05: int main(void) {
06: printf("sizeof(array1) = %lu\n", (unsigned long)(sizeof(fun1(23)-
array1)));
07: printf("sizeof(array2) = %lu\n", (unsigned long)
(sizeof(fun2(45).array2)));
08: return EXIT_SUCCESS;
09: }
----

If NO then rest of this post may be discarded.

If YES then it seems that I have found an issue
with compiler (tcc version 0.9.26).

Following example is compiled without any error/warning,
but final application generates "Segmentation Fault" in line 31.

I would like to have confirmation about correctness of this code.

--[beg]-----------------------------------------------------
C:\tcc> gawk '{printf("%02u: %s\n", NR, $0);}' main.c
01: #include <stdio.h>
02: #include <stdlib.h>
03: #include <string.h>
04: typedef struct {
05: void * ptr;
06: } bar_t;
07: typedef struct {
08: bar_t bar;
09: int * iptr;
10: } foo_t;
11: bar_t * fun(foo_t * foo_ptr) {
12: bar_t * result = NULL;
13: printf("++ fun(foo_ptr = %p)\n", (void *)foo_ptr);
14: result = &(foo_ptr->bar);
15: printf("-- fun(...) = %p\n", (void *)result);
16: return result;
17: }
18: #define TEST_MACRO(foo_ptr) \
19: ((sizeof(*((1 ? foo_ptr : foo_ptr)->iptr)) == 123) \
20: ? 12 : 34)
21: int main(void) {
22: foo_t foo;
23: memset(&foo, 0, sizeof(foo));
24: foo.bar.ptr = &foo;
25: printf("Hello World\n");
26: printf("foo.bar.ptr = %p\n", foo.bar.ptr);
27: printf("foo.iptr = %p\n", (void *)foo.iptr);
28: printf("fun(&foo) = %p\n", (void *)fun(&foo));
29: printf("fun(&foo)->ptr = %p\n", fun(&foo)->ptr);
30: printf("+++++++++++\n");
31: printf("%d\n", TEST_MACRO(((foo_t *)((fun(&foo))->ptr))));
32: printf("-----------\n");
33: return EXIT_SUCCESS;
34: }
C:\tcc>tcc -c -o main.o main.c
C:\tcc>tcc -o main.exe main.o
C:\tcc>main.exe
Hello World
foo.bar.ptr = 0012FF78
foo.iptr = 00000000
++ fun(foo_ptr = 0012FF78)
-- fun(...) = 0012FF78
fun(&foo) = 0012FF78
++ fun(foo_ptr = 0012FF78)
-- fun(...) = 0012FF78
fun(&foo)->ptr = 0012FF78
+++++++++++
<:seg-fault:>
--[eof]-----------------------------------------------------

NOTE: Simplifying this code for example in line 19:
- ((sizeof(*((1 ? foo_ptr : foo_ptr)->iptr)) == 123) \
+ ((sizeof(*(( foo_ptr )->iptr)) == 123) \
makes that issue disappears.

Regards
 
N

Noob

Maciej said:
Is following code correct (ANSI C89) ?
----
01: #include <stdio.h>
02: #include <stdlib.h>
03: struct { char array1 [100]; } * fun1(int x1);
04: struct { char array2 [200]; } fun2(int x2);
05: int main(void) {
06: printf("sizeof(array1) = %lu\n", (unsigned long)(sizeof(fun1(23)-
array1)));
07: printf("sizeof(array2) = %lu\n", (unsigned long)
(sizeof(fun2(45).array2)));
08: return EXIT_SUCCESS;
09: }
----

NB: Adding line numbers makes it impossible to paste as-is.
You could either remove them, or provide them as comments.
If YES then it seems that I have found an issue
with compiler (tcc version 0.9.26).

Yes, as far as gcc 4.6.3 can tell :)
$ gcc -std=c89 -pedantic -Wall -Wextra x.c

However, I don't find it surprising that a compiler whose main goals
are 1) simplicity and 2) compilation speed would stumble on some
corner case.

https://en.wikipedia.org/wiki/Tiny_C_Compiler

Regards.
 
J

James Kuyper

On 02/20/2013 06:54 AM, Maciej Labanowicz wrote:
....
If YES then it seems that I have found an issue
with compiler (tcc version 0.9.26).

Following example is compiled without any error/warning,
but final application generates "Segmentation Fault" in line 31.

I would like to have confirmation about correctness of this code.

--[beg]-----------------------------------------------------
C:\tcc> gawk '{printf("%02u: %s\n", NR, $0);}' main.c
01: #include <stdio.h>
02: #include <stdlib.h>
03: #include <string.h>
04: typedef struct {
05: void * ptr;
06: } bar_t;
07: typedef struct {
08: bar_t bar;
09: int * iptr;
10: } foo_t;
11: bar_t * fun(foo_t * foo_ptr) {
12: bar_t * result = NULL;

In general, I'd recommend checking whether foo_ptr is null before trying
to dereferencing it. In this particular case, it's trivial to see to
that it's never called with a null pointer argument, but in more
realistic programs, it's a useful safety measure.

Why set 'result' to NULL? You never use 'result' until after it has
already been changed to a different value. Why not simply set it to the
correct value from the beginning?
13: printf("++ fun(foo_ptr = %p)\n", (void *)foo_ptr);
14: result = &(foo_ptr->bar);
15: printf("-- fun(...) = %p\n", (void *)result);
16: return result;
17: }
18: #define TEST_MACRO(foo_ptr) \
19: ((sizeof(*((1 ? foo_ptr : foo_ptr)->iptr)) == 123) \
20: ? 12 : 34)

In general, it's a good idea to parenthesize the arguments of a macro
wherever they appear it it's expansion. You also have some unnecessary
parenthesis. I'd re-write that as

#define TEST_MACRO(foo_ptr) \
(sizeof (*(1 ? (foo_ptr) : (foo_ptr))->iptr) == 123 ? 12 : 34)
21: int main(void) {
22: foo_t foo;
23: memset(&foo, 0, sizeof(foo));
24: foo.bar.ptr = &foo;
25: printf("Hello World\n");
26: printf("foo.bar.ptr = %p\n", foo.bar.ptr);
27: printf("foo.iptr = %p\n", (void *)foo.iptr);

Your memset() of foo caused every byte of foo.iptr to be 0. This is NOT
guaranteed to leave it containing a valid pointer representation. In
particular, it's not guaranteed to be a valid representation of a null
pointer value. You should not be trying to retrieve the value of
foo.iptr until it has been set to a valid pointer value; otherwise the
behavior of your program is undefined. In principle, this issue could
explain your seg-fault below, even though the seg-fault occurs much
later - that's because there are no limits on undefined behavior.
However, in practice, it's unlikely that the representation is invalid,
and even if it were, it would be extremely unlikely that it would cause
such a long-delayed failure.
28: printf("fun(&foo) = %p\n", (void *)fun(&foo));
29: printf("fun(&foo)->ptr = %p\n", fun(&foo)->ptr);
30: printf("+++++++++++\n");
31: printf("%d\n", TEST_MACRO(((foo_t *)((fun(&foo))->ptr))));

Several of the parentheses in that macro invocation are unnecessary. The
following should be equivalent:

TEST_MACRO((foo_t*)fun(&foo)->ptr)
32: printf("-----------\n");
33: return EXIT_SUCCESS;
34: }
C:\tcc>tcc -c -o main.o main.c
C:\tcc>tcc -o main.exe main.o
C:\tcc>main.exe
Hello World
foo.bar.ptr = 0012FF78
foo.iptr = 00000000
++ fun(foo_ptr = 0012FF78)
-- fun(...) = 0012FF78
fun(&foo) = 0012FF78
++ fun(foo_ptr = 0012FF78)
-- fun(...) = 0012FF78
fun(&foo)->ptr = 0012FF78
+++++++++++
<:seg-fault:>

I don't have any good ideas about why that's happening. I don't have
tcc; the code works under gcc.
--[eof]-----------------------------------------------------

NOTE: Simplifying this code for example in line 19:
- ((sizeof(*((1 ? foo_ptr : foo_ptr)->iptr)) == 123) \
+ ((sizeof(*(( foo_ptr )->iptr)) == 123) \
makes that issue disappears.

Nor do I have any idea why that would help. Sorry that I couldn't be
more helpful.
 
M

Maciej Labanowicz

Oops, this is important issue.

After applying changes:
- ((sizeof(*((1 ? foo_ptr : foo_ptr)->iptr)) == 123) \
+ ((sizeof(*((1 ? (foo_ptr) : (foo_ptr))->iptr)) == 123) \
- memset(&foo, 0, sizeof(foo));
+ foo.iptr = NULL;
"segfault" is still present :)

Thanks for Your comments.
 
S

Shao Miller


Hello, are you going to follow up on your last thread?
Is following code correct (ANSI C89) ?

That depends on what you mean. It depends on casts to 'unsigned long'
from 'size_t', which can be criticized.
----
01: #include <stdio.h>
02: #include <stdlib.h>
03: struct { char array1 [100]; } * fun1(int x1);
04: struct { char array2 [200]; } fun2(int x2);
05: int main(void) {
06: printf("sizeof(array1) = %lu\n", (unsigned long)(sizeof(fun1(23)->array1)));

You might as well have used 'fun1(0)', since there is no call:

printf(
"sizeof(array1) = %lu\n",
(unsigned long) sizeof fun1(0)->array1
);
07: printf("sizeof(array2) = %lu\n", (unsigned long)
(sizeof(fun2(45).array2)));
08: return EXIT_SUCCESS;
09: }

No. The line numbers do not belong. Everyone's got their own opinion,
but your use of line numbers causes someone wishing to copy and paste
your code to perform steps that could be avoided if the line numbers
were omitted. I also find it harder to read, personally.
If YES then it seems that I have found an issue
with compiler (tcc version 0.9.26).

Following example is compiled without any error/warning,
but final application generates "Segmentation Fault" in line 31.

I would like to have confirmation about correctness of this code.

--[beg]-----------------------------------------------------
C:\tcc> gawk '{printf("%02u: %s\n", NR, $0);}' main.c

I beg you to drop line numbers from your posts. :)
01: #include <stdio.h>
02: #include <stdlib.h>
03: #include <string.h>
04: typedef struct {
05: void * ptr;
06: } bar_t;
07: typedef struct {
08: bar_t bar;
09: int * iptr;
10: } foo_t;
11: bar_t * fun(foo_t * foo_ptr) {
12: bar_t * result = NULL;
13: printf("++ fun(foo_ptr = %p)\n", (void *)foo_ptr);
14: result = &(foo_ptr->bar);
15: printf("-- fun(...) = %p\n", (void *)result);
16: return result;
17: }
18: #define TEST_MACRO(foo_ptr) \
19: ((sizeof(*((1 ? foo_ptr : foo_ptr)->iptr)) == 123) \
20: ? 12 : 34)

Taking this apart, the macro appears to expand to:

(E1 ? 12 : 34)

E1 is:

(E2 == 123)

E2 is:

sizeof (E3)

Unless VLAs are involved, which they do not appear to be, then E3 should
not involve any side effects.
21: int main(void) {
22: foo_t foo;
23: memset(&foo, 0, sizeof(foo));

Redundant parentheses for the 'sizeof' operator. Why not use the
following?:

((memset)((&foo), (0), (sizeof (foo))));
24: foo.bar.ptr = &foo;
25: printf("Hello World\n");
26: printf("foo.bar.ptr = %p\n", foo.bar.ptr);
27: printf("foo.iptr = %p\n", (void *)foo.iptr);

How do you know that 'foo.iptr' has a pointer value stored? Whether or
not your 'memset' did that is implementation-defined.
28: printf("fun(&foo) = %p\n", (void *)fun(&foo));
29: printf("fun(&foo)->ptr = %p\n", fun(&foo)->ptr);
30: printf("+++++++++++\n");
31: printf("%d\n", TEST_MACRO(((foo_t *)((fun(&foo))->ptr))));
32: printf("-----------\n");
33: return EXIT_SUCCESS;
34: }
C:\tcc>tcc -c -o main.o main.c
C:\tcc>tcc -o main.exe main.o
C:\tcc>main.exe
Hello World
foo.bar.ptr = 0012FF78
foo.iptr = 00000000
++ fun(foo_ptr = 0012FF78)
-- fun(...) = 0012FF78
fun(&foo) = 0012FF78
++ fun(foo_ptr = 0012FF78)
-- fun(...) = 0012FF78
fun(&foo)->ptr = 0012FF78
+++++++++++
<:seg-fault:>
--[eof]-----------------------------------------------------

NOTE: Simplifying this code for example in line 19:
- ((sizeof(*((1 ? foo_ptr : foo_ptr)->iptr)) == 123) \
+ ((sizeof(*(( foo_ptr )->iptr)) == 123) \
makes that issue disappears.

As mentioned before, if undefined behaviour is invoked, code changes
which should not have an apparent change in behaviour, sometimes do.

In this case though, perhaps there is a bug. C99 VLA support was just
introduced on Feb. 15th (5 days ago), so it would not be surprising.
Perhaps you could make a more minimal test case and report it to Thomas
Preud'homme.
 
B

Barry Schwarz

Oops, this is important issue.

After applying changes:
- ((sizeof(*((1 ? foo_ptr : foo_ptr)->iptr)) == 123) \
+ ((sizeof(*((1 ? (foo_ptr) : (foo_ptr))->iptr)) == 123) \
- memset(&foo, 0, sizeof(foo));
+ foo.iptr = NULL;
"segfault" is still present :)

You really should provide the changes in context. All I can tell from
this is you don't have enough right parentheses.

Unless you have hidden a variable length array in there somewhere,
sizeof does not evaluate its operand. What does your sizeof
expression really mean? Do you really think the parentheses change
anything?

Have you verified the trick works with a simple example such as

int main(void){
char c;
double d;
int i1, i2;
i1 = sizeof(1 ? c : d);
i2 = sizeof(0 ? c : d);
printf("%d %d\n", i1, i2);
return 0;
}

If you break each + and - term into its own statement, such as
x = -sizeof(*(1 ? foo...
x += sizeof(*(1 ? (foo...
x -= memset(...
and use a debugger you can determine where the seg fault occurs.
 
S

Shao Miller

You really should provide the changes in context. All I can tell from
this is you don't have enough right parentheses.

Unless you have hidden a variable length array in there somewhere,
sizeof does not evaluate its operand. What does your sizeof
expression really mean? Do you really think the parentheses change
anything?

Have you verified the trick works with a simple example such as

int main(void){
char c;
double d;
int i1, i2;
i1 = sizeof(1 ? c : d);
i2 = sizeof(0 ? c : d);
printf("%d %d\n", i1, i2);
return 0;
}

If you break each + and - term into its own statement, such as
x = -sizeof(*(1 ? foo...
x += sizeof(*(1 ? (foo...
x -= memset(...
and use a debugger you can determine where the seg fault occurs.

I'd guess that the TinyCC's 5-day-old VLA support probably caused the
conditional operator to evaluate its operands even if the conditional
expression is the operand of the 'sizeof' operator. Just a guess, though.

#include <stdio.h>

int (* foo(void))[10] {
printf("Uh oh\n");
return 0;
}

int main(void) {
return sizeof *(1 ? foo() : foo());
}

If this outputs "Uh oh", then uh oh.
 
J

James Kuyper

You really should provide the changes in context. All I can tell from
this is you don't have enough right parentheses.

In the original context, the first line that she changed was part of the
definition of a macro, which was followed by this line:

? 12 : 34)

Which brings the parentheses into balance.
Unless you have hidden a variable length array in there somewhere,
sizeof does not evaluate its operand. What does your sizeof
expression really mean? Do you really think the parentheses change
anything?

The additional parentheses were my suggestion, as a safety measure, one
that doesn't matter in her example program, but which might matter in a
more complicated one.

My guess is that the original code had all sorts of complexities that
are missing from this version, and that the original version of
TEST_MACRO actually made sense in that environment. Now, after much
simplification, TEST_MACRO()'s only remaining purpose is to trigger the
segfault, so she can try to determine why the segfault is occurring.
Have you verified the trick works with a simple example such as

int main(void){
char c;
double d;
int i1, i2;
i1 = sizeof(1 ? c : d);
i2 = sizeof(0 ? c : d);
printf("%d %d\n", i1, i2);
return 0;
}

If you break each + and - term into its own statement, such as
x = -sizeof(*(1 ? foo...
x += sizeof(*(1 ? (foo...
x -= memset(...
and use a debugger you can determine where the seg fault occurs.

There's really not much choice in the matter. There's only one tricky
feature that follows the last successfully executed printf() call, and
that's the expansion of TEXT_MACRO(). The result of that expansion is
very complicated, but as I understand it, it should all boil down to
(sizeof(int)==123 ? 12 : 34). Therefore, there's no plausible way for it
to result in a segfault unless someone's misunderstanding something -
either me, or the implementors of tinycc.
 
S

Shao Miller

You really should provide the changes in context. All I can tell from
this is you don't have enough right parentheses.

Unless you have hidden a variable length array in there somewhere,
sizeof does not evaluate its operand. What does your sizeof
expression really mean? Do you really think the parentheses change
anything?

Have you verified the trick works with a simple example such as

int main(void){
char c;
double d;
int i1, i2;
i1 = sizeof(1 ? c : d);
i2 = sizeof(0 ? c : d);
printf("%d %d\n", i1, i2);
return 0;
}

If you break each + and - term into its own statement, such as
x = -sizeof(*(1 ? foo...
x += sizeof(*(1 ? (foo...
x -= memset(...
and use a debugger you can determine where the seg fault occurs.

I'd guess that the TinyCC's 5-day-old VLA support probably caused the
conditional operator to evaluate its operands even if the conditional
expression is the operand of the 'sizeof' operator. Just a guess, though.

#include <stdio.h>

int (* foo(void))[10] {
printf("Uh oh\n");
return 0;
}

int main(void) {
return sizeof *(1 ? foo() : foo());
}

If this outputs "Uh oh", then uh oh.

Then again, it gripes that the second operand of the conditional (the
zero) needs to be a pointer, for the code below:

int main(void) {
return sizeof *(1 ? 0 : (int *) 0);
}

So the implementation could use some improvements.
 
M

Maciej Labanowicz

Redundant parentheses for the 'sizeof' operator.

This is a style that I prefer.
My experience shows that sometimes using of parentheses is a good
idea.
It makes that the code review is simpler, example:
- if (a + b * c)
+ if (a + (b * c))
BTW: I remember that my university teacher has always complaining
about the redundant parentheses, he has mentioned that I should
know perfectly priority of operators.
After that I have started to work in private company,
where such practice is forbidden.
Parentheses are your friend.

Another example of the redundant code is the
initialization of 'result' in function 'fun'.
But this is also a style that I prefer.
Any variable should be initialized at begining.
For example, a someone has fixed this code with:
if (foo_ptr) {
result = &(foo_ptr->bar);
}
then function is still correctly working.
In case the initialization is redundant then
an optimizer will remove the code during compilation process.
It is very hard to diagnose bugs that depends on the uninitialized
variable, especially
if it occures only ones per a week and only on client side (taken from
real life).

Regards
 
J

James Kuyper

This is a style that I prefer.
My experience shows that sometimes using of parentheses is a good
idea.
It makes that the code review is simpler, example:
- if (a + b * c)
+ if (a + (b * c))
BTW: I remember that my university teacher has always complaining
about the redundant parentheses, he has mentioned that I should
know perfectly priority of operators.
After that I have started to work in private company,
where such practice is forbidden.
Parentheses are your friend.

Putting in so many unnecessary parentheses made your expressions very
confusing - it took me a lot longer to figure them out than it should have.
Another example of the redundant code is the
initialization of 'result' in function 'fun'.
But this is also a style that I prefer.
Any variable should be initialized at begining.

In this case, you can trivially give the variable it's final value at
the point of definition, so why not do so?

bar_t * fun(foo_t * foo_ptr) {
bar_t * result = &(foo_ptr->bar);
printf("++ fun(foo_ptr = %p)\n", (void *)foo_ptr);
printf("-- fun(...) = %p\n", (void *)result);
return result;
}

I'm opposed to the idea of initializing a variable with anything other
than a value that is intended to be used. I will delay definition of the
variable as long as possible in order to make this happen. In C99, which
allows inter-mixed declarations and statements, it's possible to delay
definition a great deal. However, if I reach the point where it's no
longer possible to delay definition of the variable, and I still can't
give it a value that's actually intended to be used, I leave it
uninitialized.

My reason for this policy is that most modern compilers do a very good
job of checking whether or not a variable has been given a value before
it's first use. By initializing it with a value never intended to be
used, you are disabling that check. You might end up unintentionally
using the value that was never intended to be used.
For example, a someone has fixed this code with:
if (foo_ptr) {
result = &(foo_ptr->bar);
}
then function is still correctly working.
In case the initialization is redundant then
an optimizer will remove the code during compilation process.

Not always - but redundancy isn't the primary reason I'm opposed to this
practice.
It is very hard to diagnose bugs that depends on the uninitialized
variable, especially
if it occures only ones per a week and only on client side (taken from
real life).

In my experience, modern compilers are very good at diagnosing possible
use of uninitialized variables. Achieving certainty about whether an
uninitialized variable will be used is exactly equivalent to solving the
halting problem, but if you allow for a small number of false positives,
such problems can be identified quite easily - in case of doubt, flag it
as dangerous. If the code's so confusing that the compiler can't be
certain whether the variable will be used while uninitialized, it's too
complicated for me to be justifiably certain about it, either. In that
case, I will relent and give it an initial value that's not intended to
be used - usually one deliberately chosen to produce a spectacular
failure if it ever does get used. I would never choose a value that's
intended to allow the routine to keep working normally - if something
has happened that wasn't supposed to happen, I want to know about it as
soon as possible.
 
S

Shao Miller

This is a style that I prefer.

Ok, I respect that, then. If I could make a tiny suggestion: Since you
use 'if (', perhaps you could use 'sizeof (' instead of 'sizeof('? This
tiny change would have some infinitesimally smaller chance of
perpetuating 'sizeof' confusion for people who try to learn C from
source code... Assuming that you use 'func(' and 'macro(' rather than
'func (' and 'macro ('.
My experience shows that sometimes using of parentheses is a good
idea.
It makes that the code review is simpler, example:
- if (a + b * c)
+ if (a + (b * c))

Yes, sometimes I introduce redundant parentheses with infix operators,
too, if it seems to make code more readable, as you say. Your example
is nice.
BTW: I remember that my university teacher has always complaining
about the redundant parentheses, he has mentioned that I should
know perfectly priority of operators.
After that I have started to work in private company,
where such practice is forbidden.

Which practice? This doesn't make sense, to me... Adding redundant
parentheses is a practice, because you have to choose where to put them.
Choosing not to add them is not a practice, because you simply don't
make any choices about where to add them. Can you explain this policy a
bit more? Does the policy explain exactly when and where to add them?
Parentheses are your friend.

Agreed that they can be, but only if they contribute to clarity. In the
case of the 'sizeof' operator, they've confused people into thinking
it's a function or a macro. If 'sizeof' had been '_Sizeof' all along,
then at least it would have a funny look that learners might think twice
about. As it is, code like:

x = printf("Hello, world!\n");
x = sizeof("Hello, world!\n");

has two lines which look so similar that a lot of novices get 'sizeof'
and 'strlen' confused. If you can teach something with your code, then
your code can go beyond functional and readable, towards instructional.
That's not important to everyone.
Another example of the redundant code is the
initialization of 'result' in function 'fun'.
But this is also a style that I prefer.

Ok. Do you use special values as a sort of "trap", so that clients can
report observations and it helps you to debug the problem?
Any variable should be initialized at begining.
For example, a someone has fixed this code with:
if (foo_ptr) {
result = &(foo_ptr->bar);
}
then function is still correctly working.
In case the initialization is redundant then
an optimizer will remove the code during compilation process.
It is very hard to diagnose bugs that depends on the uninitialized
variable, especially
if it occures only ones per a week and only on client side (taken from
real life).

Maybe that's why you are typing posts involving some of the subtleties
of C? So that you can fix a compiler that isn't good at warning about
use of uninitialized objects? I can think of a couple that do a pretty
good job of it, but obviously that's not universal.
 
J

Jorgen Grahn

.
Another example of the redundant code is the
initialization of 'result' in function 'fun'.
But this is also a style that I prefer.
Any variable should be initialized at begining.

Argh! A lot of people seem to do it, but I really dislike that
practice.
For example, a someone has fixed this code with:
if (foo_ptr) {
result = &(foo_ptr->bar);
}
then function is still correctly working.
In case the initialization is redundant then
an optimizer will remove the code during compilation process.
It is very hard to diagnose bugs that depends on the uninitialized
variable, especially
if it occures only ones per a week and only on client side (taken from
real life).

Three things:

- Like others noted, modern compilers emit warnings about
uninitialized variables being used. That way you don't have to
clutter your code with 'int i = 0;', and in a sense squeeze more
information into your code: when you see 'int i;' but no warning,
you know[1] i will get a value later, in all relevant code paths.

'int i=0;' on the other hand may mean that 0 is a true
initialization value -- or just a dummy.

- The initializion technique you describe doesn't help avoid bugs;
it helps hiding bugs by making their symptoms more subtle.

What actually happens is that person A chooses a 'safe' initial
value, and person B creates the bug: uses the variable before its
/real/ initialization has been done. There are no guarantees that A
made the choice that matches B's assumptions.

Now you have a bug that neither the compiler nor tools like Valgrind
can detect: the program doesn't crash, it just produces results
which don't match the expected ones.

- All of this is less of a problem in C99, where you can usually
declare your variable where it's first used, i.e. where you know how
to initialize it. You should argue for C99 instead.

/Jorgen

[1] "Know" is too strong. If you call 'foo(&i)', the compiler cannot
generally know if foo() initializes i, and has to assume it does.
 
M

Maciej Labanowicz

Can you explain this policy a bit more?
Does the policy explain exactly when and where to add them?
In general it should be added only for clarification of the code.
Main requirements are confidential, so I can not share with you.
Maybe that's why you are typing posts involving some of the subtleties
of C?  So that you can fix a compiler that isn't good at warning about
use of uninitialized objects?  I can think of a couple that do a pretty
good job of it, but obviously that's not universal.
I agree that modern compilers are able to detect most of the problems.
It is great.

Example of the code that modern compiler
is silent about uninitialized variable:
$ gawk '{printf("%02u: %s\n", NR, $0);}' test.c
01: extern void bar(int *);
02: void foo(void) {
03: int x;
04: bar(&x);
05: }
$ gcc --version | head -n1
gcc (Ubuntu/Linaro 4.7.2-11precise2) 4.7.2
$ gcc -W -Wall -ansi -pedantic -Werror -Wextra -Wuninitialized -
Wmissing-field-initializers -Wjump-misses-init -Winit-self -c test.c
$

If there is an -W option to GCC that detects the uninitialized
variable please give the info.

I don't want to convince anyone to use that 'style'.
I know that using it makes the life of programmer is easier in real
world.

Regards
 
K

Keith Thompson

Maciej Labanowicz said:
Example of the code that modern compiler
is silent about uninitialized variable:
$ gawk '{printf("%02u: %s\n", NR, $0);}' test.c
01: extern void bar(int *);
02: void foo(void) {
03: int x;
04: bar(&x);
05: }
$ gcc --version | head -n1
gcc (Ubuntu/Linaro 4.7.2-11precise2) 4.7.2
$ gcc -W -Wall -ansi -pedantic -Werror -Wextra -Wuninitialized -
Wmissing-field-initializers -Wjump-misses-init -Winit-self -c test.c
$

If there is an -W option to GCC that detects the uninitialized
variable please give the info.

"-O3" helps the compiler find some such issues, because of the
information collected during optimization, but there's no real
problem in this case.

There is no reference to the uninitialized *value* of x. If the
definition of bar is:

void bar(int *x) { *x = 42; }

then the code is perfectly legitimate.

Of course it's possible that bar could attempt to read the value of *x
before writing to it, and I don't know a way to make gcc warn about
that. Cross-function optimization is difficult; optimization across
translation units is even more so.

[...]
 
N

Noob

Maciej said:
Example of the code that modern compiler is silent about uninitialized variable:
$ gawk '{printf("%02u: %s\n", NR, $0);}' test.c

For the sake of ZEUS! Please change %02u: to /*%02u:*/
01: extern void bar(int *);
02: void foo(void) {
03: int x;
04: bar(&x);
05: }

If there is an option to GCC that detects the uninitialized
variable please give the info.

NB: There is no error, at this point.
x must be initialized ONLY IF 'bar' expects it to be...

$ cat f1.c
extern void bar(int *p);
int main(void) { int x; bar(&x); return x; }
$ cat f2.c
void bar(int *p) { *p += 42; }
$ gcc -Wall -Wextra -O2 -flto f1.c f2.c
In file included from :0:0:
f1.c: In function ‘main’:
f2.c:1:23: warning: ‘x’ is used uninitialized in this function [-Wuninitialized]
f1.c:2:22: note: ‘x’ was declared here

http://gcc.gnu.org/wiki/LinkTimeOptimization

NB: the "standard" signature delimiter is DASH DASH SPACE NEWLINE
 
M

Maciej Labanowicz

$ gcc -Wall -Wextra -O2 -flto f1.c f2.c

Nice to hear about that option.

Full example (more complicated):
--------------------------------
$ cat main.c
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
typedef struct {
int * p;
} strct;
void bar(strct * s) {
strct copy;
memcpy(&copy, s, sizeof (strct));
if (*copy.p < 1) {
*copy.p = 200;
}
else {
*copy.p = rand() % 200;
}
}
int foo(void) {
int x;
strct s;
s.p = &x;
bar(&s);
return (x > 50) && (x < 100);
}
int fill(void) {
int x = 10;
strct s;
s.p = &x;
bar(&s);
return 0;
}
int main(void) {
srand((unsigned)time(NULL));
fill();
if (foo()) {
printf("OK, HDD format has started.\n");
}
return EXIT_SUCCESS;
}
$ gcc -Wall -Wextra -O2 -flto main.c -o a.out
$ ./a.out
$
 
A

Anders Wegge Keller

Noob said:
WTF is that supposed to mean?

If 2 mean can dig one ditch in a week, how many men does it take
to dig the same ditch in an hour?

That question is really about the same, ie. applying purely
mathematical models to real-world problems, where practical
constraints must be observed. Google "Chinese horde debugging" and/or
read "The mythical man-month" for further enlightenment.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,482
Members
44,901
Latest member
Noble71S45

Latest Threads

Top