views:

133

answers:

5
int bar = 2;
if (bar)
{
   int bar;
}

Neither gcc or Clang manages to issue a warning (or error) for this, and the program crashes immediately on launch. Is there a good reason for this? It doesn't seem like it would be something hard to catch. It's the basics of block scoping: the nested scope inherits the names of the enclosing block...

Any explanations?

EDIT: It turns out the crash was due to using Clang. I've tested many times back and forth, and it seems certain that the combination of the variable redefinition and Clang causes the crash. However, I haven't been able to reproduce the crash in a test project, so go figure.

The problem turned out to be Objective-C related. As Jonathan Leffler points out doing ´int bar = bar´ in the inner scope initializes the variable from itself, and that's what causes the problem, when the initialization is done via an Objective-C method call.

The following shows the bug in action:

-(void)crasher
{
   NSNumber* bar = [NSNumber numberWithInt:2];
   if (bar)
   {
      NSString* bar = [self doit:bar];
   }
}

-(NSString*)doit:(NSNumber*)num
{
   NSString* str = [num stringValue];   // This line causes the crash
   return str;
}

Note that doing something similar in pure C does not produce a crash:

int bar = 2;
if (bar)
{
   char buff[10];
   int bar = sprintf(buff, "%d",bar);       
}
+8  A: 

There's nothing to catch here. The variable in the inner block is a completely different variable, that hides the variable in the outer block. This is a perfectly standard feature of the language that has been there since the beginning of times.

The crash you are experiencing has absolutely nothing to do with the code you posted. Unless you made a mistake in your code, working with the inner variable while assuming that your are working with the outer one.

AndreyT
See edit. The crash was due to initialization from the same variable, done with an Objective-C message send. However, also see the bit about Clang.
Felixyz
@Felixyz: Wel, your original code sample did not include any "initializations from the same variable"...
AndreyT
@AndreyT: You are completely right! That's the exact reason why I made an edit, to include more information. I am simply following the recommendations of the SO faq, which states: "Edit your question to provide status and progress updates. Document your own continued efforts to answer your question."
Felixyz
+1  A: 
$ gcc 1.c

$ gcc -Wall 1.c
1.c: In function ‘main’:
1.c:6: warning: unused variable ‘bar’

$ cat 1.c
int main()
{
    int bar = 2;
    if (bar)
    {
        int bar;
    }
    return 0;
}

$ ./a.out ; echo $?
0

Compiles for me - with a warning under -Wall. And the program runs ok.

You've declared two variables, one called bar and the other one also called bar. The compiler doesn't care, as long as they're in different scopes.

Douglas Leeder
+2  A: 

It's also a basic of block scoping that re-defining a name from an outer scope in an inner scope isn't an error -- it's normal and expected. Doing otherwise would basically get us back to the bad old days of ancient BASIC dialects that only had global variables.

Jerry Coffin
+4  A: 

It is the basic of nested scope: inside a nested scope you can shadow something declared in an outer scope. gcc has an option to get a warning for this (-Wshadow), but it isn't activated either by -Wall nor -Wextra: the warning can appear without change in the code (an header has now a definition at global scope for an identifier used in a function).

AProgrammer
+1  A: 

Extending the answer given by Douglas Leeder:

#include <stdio.h>
static int xx(int foo)
{
    int bar = 2;
    if (foo > bar)
    {
        int foo = bar;
        int bar = bar;
        printf("inner: foo = %d, bar = %d\n", foo, bar);
    }
    printf("outer: foo = %d, bar = %d\n", foo, bar);
    return bar;
}
int main(void)
{
    xx(13);
    return(0);
}

Note that the inner bar is initialized from itself - which gives undefined behaviour. But on MacOS X 10.6.2 (GCC 4.2.1) I get:

inner: foo = 2, bar = 0
outer: foo = 13, bar = 2

Variant 1: Stack trampling - A

Interestingly, I get the same output from this code, with a stack trampling function, regardless of whether I declare i before or after a.

inner: foo = 2, bar = 20
outer: foo = 13, bar = 2

Code:

#include <stdio.h>
static void modify_stack(void)
{
    int a[20];
    int i;
    for (i = 0; i < 20; i++)
    {
        a[i] = 0xFFFFFFFF ^ i;
        printf("a[i] = 0x%08X\n", a[i]);
    }
}
static int xx(int foo)
{
    int bar = 2;
    if (foo > bar)
    {
        int foo = bar;
        int bar = bar;
        printf("inner: foo = %d, bar = %d\n", foo, bar);
    }
    printf("outer: foo = %d, bar = %d\n", foo, bar);
    return bar;
}
int main(void)
{
    modify_stack();
    xx(13);
    return(0);
}

Since the behaviour is undefined, this result is fine.

Variant 2: Stack trampling - B

#include <stdio.h>
static int modify_stack(void)
{
    int a[20];
    int i;
    for (i = 0; i < 20; i++)
    {
        a[i] = 0xFFFFFFFF ^ i;
        printf("a[i] = 0x%08X\n", a[i]);
    }
    i = a[13];
    return(i);
}
static int xx(int foo)
{
    int bar = 2;
    if (foo > bar)
    {
        int foo = bar;
        int bar = bar;
        printf("inner: foo = %d, bar = %d\n", foo, bar);
    }
    printf("outer: foo = %d, bar = %d\n", foo, bar);
    return bar;
}
int main(void)
{
    int i = modify_stack();
    xx(13);
    return(i & 0xFF);
}

Output (apart from data printed in loop):

inner: foo = 2, bar = -14
outer: foo = 13, bar = 2
Jonathan Leffler
It's interesting that the inner bar in your example is initialized to 0, whereas in my new example, which reproduces the bug, the inner bar (which is sent to doit:) is not. Even testing num for null value crashes.
Felixyz
@Felixyz - the zero is a quirk of the system I'm running on and the compiler I am using. One of the interesting things about undefined behaviour is that it can look reasonable, especially in test cases. I added a 'stack trampler' function that had a variable counting from 1 to less than 20 and when rerun, the inner 'bar' was initialized to 20 - a leftover from the stack trampler. GCC reordered the array and the index to avoid overflows - I got the same result whether I put the index variable before or after the array.
Jonathan Leffler