tags:

views:

622

answers:

8

I've just encountered a weird problem, I'm trying to printf an integer variable, but I forgot to specify the variable name, i.e.

printf("%d");

instead of

printf("%d", integerName);

Surprisingly the program compiles, there is output and it is not random. In fact, it happens to be the very integer I wanted to print in the first place, which happens to be m-1.

The errorneous printf statement will consistently output m-1 for as long as the program keeps running... In other words, it's behaving exactly as if the statement reads

printf("%d", m-1);

Anybody knows the reason behind this behaviour? I'm using g++ without any command line options.

#include <iostream>
#define maxN 100
#define ON 1
#define OFF 0

using namespace std;

void clearArray(int* array, int n);
int fillArray(int* array, int m, int n);

int main()
{
    int n = -1, i, m;
    int array[maxN];
    int found;

    scanf("%d", &n);

    while(n!=0)
    {
     found=0;
     m = 1;
     while(found!=1)
     {
      if(m != 2 && m != 3 && m != 4 && m != 6 && m != 12)
      {
       clearArray(array, n);
       if(fillArray(array, m, n) == 0)
       {
        found = 1;
       }
      }
      m++;
     }

     printf("%d\n");

     scanf("%d", &n);
    }

    return 0;
}

void clearArray(int* array, int n)
{
    for(int i = 1; i <= n; i++)
     array[i] = ON;
}

int fillArray(int* array, int m, int n)
{
    int i = 1, j, offCounter = 0, incrementCounter;

    while(offCounter != n)
    {
     if(*(array+i)==ON) 
     {
      *(array+i) = OFF;
      offCounter++;  
     }
     else 
     {
      j = 0;
      while((*array+i+j)==OFF)
      {
       j++;
      }
      *(array+i+j) = OFF;
      offCounter++;   
     }
     if(*(array+13) == OFF && offCounter != n) return 1;
     if(offCounter ==n) break;

     incrementCounter = 0;  
     while(incrementCounter != m)
     {
      i++;
      if(i > n) i = 1;
      if(*(array+i) == ON) incrementCounter++; 
     }  
    }

    return 0;
}
A: 

You found one behavior. It could have been any other behavior, including an invalid memory access.

Otávio Décio
Yes.. I realised that it might not have been m-1 for all cases.
Yew Long
+8  A: 

What happens looks like this.

printf("%d", m);

On most systems the address of the string will get pushed on the stack, and then 'm' as an integer (assuming it's an int/short/char). There is no warning because printf is basically declared as 'int printf(const char *, ...);' - the ... meaning 'anything goes'.

So since 'anything goes' some odd things happen when you put variables there. Any integral type smaller than an int goes as an int - things like that. Sending nothing at all is ok as well.

In the printf implementation (or at least a 'simple' implementation) you will find usage of va_list and va_arg (names sometime differ slightly based on conformance). These are what an implementation uses to walk around the '...' part of the argument list. Problem here is that there is NO type checking. Since there is no type checking, printf will pull random data off the execution stack when it looks at the format string ("%d") and thinks there is supposed to be an 'int' next.

Random shot in the dark would say that the function call you made just before printf possibly passed 'm-1' as it's second parm? That's one of many possibilities - but it would be interesting if this happened to be the case. :)

Good luck.

By the way - most modern compilers (GCC I believe?) have warnings that can be enabled to detect this problem. Lint does as well I believe. Unfortunately I think with VC you need to use the /analyze flag instead of getting for free.

Joe
I see. Normally I wouldn't be bothered and just treat it as garbage value, but since it just so happened to output sensible data I thought maybe there is something more. You've made it very clear, thank you for your help Joe.
Yew Long
gcc's flag to show this problem is -Wformat
Chris Young
The parameters usually get pushed on the stack from right to left. Then when the string is popped, it pops from the stack as it needs parameters.
Cade Roux
Not true Cade. That's right for stdcall - but for vararg cdecl calling convention there would be no way to find the 'first' parameter without walking through all the ... parms. cdecl/vararg goes left to right.
Joe
In order to ensure that the first parameter is the one at the top of the stack, the arguments are pushed from right to left. Same in cdecl and stdcall. The only difference between cdecl and stdcall is who's responsible for cleaning up the stack.
Rob Kennedy
Rob - I stand humbled. I have no idea what I was thinking - of course you are right. Sorry Cade.
Joe
+1  A: 

It got an int off the stack.

http://en.wikipedia.org/wiki/X86_calling_conventions

Cade Roux
+1  A: 

You're peering into the stack. Change the optimizer values, and this may change. Change the order of the declarations your variables (particularly) m. Make m a register variable. Make m a global variable.

You'll see some variations in what happens.

This is similar to the famous buffer overrun hacks that you get when you do simplistic I/O.

S.Lott
+6  A: 

You say that "surprisingly the program compiles". Actually, it is not surprising at all. C & C++ allow for functions to have variable argument lists. The definition for printf is something like this:

int printf(char*, ...);

The "..." signifies that there are zero or more optional arguments to the function. In fact, one of the main reasons C has optional arguments is to support the printf & scanf family of functions.

C has no special knowledge of the printf function. In your example:

printf("%d");

The compiler doesn't analyse the format string and determine that an integer argument is missing. This is perfectly legal C code. The fact that you are missing an argument is a semantic issue that only appears at runtime. The printf function will assume that you have supplied the argument and go looking for it on the stack. It will pick up whatever happens to be on there. It just happens that in your special case it is printing the right thing, but this is an exception. In general you will get garbage data. This behaviour will vary from compiler to compiler and will also change depending on what compile options you use; if you switch on compiler optimisation you will likely get different results.

As pointed out in one of the comments to my answer, some compilers have "lint" like capabilities that can actually detect erroneous printf/scanf calls. This involves the compiler parsing the format string and determining the number of extra arguments expected. This is very special compiler behaviour and will not detect errors in the general case. i.e. if you write your own "printf_better" function which has the same signature as printf, the compiler will not detect if any arguments are missing.

Mike Thompson
I didn't know that the compiler doesn't analyse the printf arguments... Thanks for telling me
Yew Long
You can tell the compiler to check the arguments. We know the compiler in this case is g++, so use the -Wformat option, which is included in -Wall.
Rob Kennedy
Using the -Wformat would be a fairly non-standard extension (albeit a useful one). Presumably this only works for a predefined list of functions.
Mike Thompson
GCC lets you annotate your own functions as being printf/scanf like.doesn't let you define new formats though
Hasturkun
+1  A: 

While I would highly doubt this would result in a memory violation, the integer you get is undefined garbage.

Joshua
+1  A: 

If you do this:

func() 
{
    int x, y;
    x = 5;
    y = 7;

    printf( "%d\n");
    printf( "%d %d\n");
}

Output will be (on most compilers):

5

5 7

OR

7

7 5

Understand why this so, Grasshopper, and you will be a real C programmer!

Tim Ring
A: 

If you do this:

func() { int x, y; x = 5; y = 7;

printf( "%d\n");
printf( "%d %d\n");

} Output will be (on most compilers):

5

5 7

OR

7

7 5

Understand why this so, Grasshopper, and you will be a real C programmer

I'm looking for the answer to this post............please if you now provide the answer...