Jump to content


Check out our Community Blogs

Register and join over 40,000 other developers!


Recent Status Updates

View All Updates

Photo
- - - - -

[C] Linked list affected by printf()?

pointer linked list printf

Best Answer gonerogue, 18 March 2014 - 07:11 AM

The code is not correct, since you are storing the address of a local variable, inside the function insertItem():

...
struct listItem newItem = {newValue, listPtr->next};
listPtr->next = &newItem;

Usually, the pointers stored in data structures points to dinamically allocated objects (on heap).

 

Using valgrind also details the problem:

 

=1788== Invalid read of size 4
==1788==    at 0x8048464: main (test.c:25)
==1788==  Address 0xbe990458 is just below the stack ptr
.  To suppress, use: --workaround-gcc296-bugs=yes

 

A similar problem was discussed in this thread:

http://forum.codecal...-in-c-function/

Go to the full post


This topic has been archived. This means that you cannot reply to this topic.
11 replies to this topic

#1 Docom

Docom

    CC Lurker

  • New Member
  • Pip
  • 5 posts

Posted 18 March 2014 - 04:52 AM

I'm trying to code a linked list in C, and I'm encountering a strange problem: The value of an item in the list seems to be affected by the presence of a printf() statement. Here's my code:

#include <stdio.h>

// define linked list containing integers
struct listItem
{
    int value;
    struct listItem *next;
};

// insert into linked list
void insertItem (int newValue, struct listItem *listPtr)
{
    struct listItem newItem = {newValue, listPtr->next};
    listPtr->next = &newItem;
    printf("\n"); // This is the problematic printf()
}

int main (void)
{
    struct listItem firstItem = {1};
    struct listItem *listPtr = &firstItem;

    insertItem(2, listPtr);

    printf("Values:\n%d\n%d\n\n", listPtr->value, listPtr->next->value);
    printf("Addresses:\n%d\n%d\n", (int) listPtr, (int) listPtr->next);
    return 0;
}

The output of that is this:


Values:
1
2

Addresses:
2293296
2293312

If I comment out that printf() I marked then the output changes to this:

Values:
1
1999514400

Addresses:
2293296
2293312

I'm running this on Win7, but in Linux on a VM I'm getting similar strange results with the other printf()s. What am I missing here?


Edited by Docom, 18 March 2014 - 04:57 AM.


#2 gonerogue

gonerogue

    CC Addict

  • Advanced Member
  • PipPipPipPipPip
  • 197 posts

Posted 18 March 2014 - 07:11 AM   Best Answer

The code is not correct, since you are storing the address of a local variable, inside the function insertItem():

...
struct listItem newItem = {newValue, listPtr->next};
listPtr->next = &newItem;

Usually, the pointers stored in data structures points to dinamically allocated objects (on heap).

 

Using valgrind also details the problem:

 

=1788== Invalid read of size 4
==1788==    at 0x8048464: main (test.c:25)
==1788==  Address 0xbe990458 is just below the stack ptr
.  To suppress, use: --workaround-gcc296-bugs=yes

 

A similar problem was discussed in this thread:

http://forum.codecal...-in-c-function/


Edited by gonerogue, 18 March 2014 - 07:16 AM.


#3 Docom

Docom

    CC Lurker

  • New Member
  • Pip
  • 5 posts

Posted 18 March 2014 - 07:58 AM

I figured that much. Why does printf() change the result? Does it force the stack frame to remain even after insertItem() finishes?



#4 0xDEADBEEF

0xDEADBEEF

    CC Devotee

  • Senior Member
  • PipPipPipPipPipPip
  • 790 posts

Posted 18 March 2014 - 08:23 AM

Someone might know exactly what's going on but, I'd recon it would be very specific to the compiler/system.

 

you'd probably see more if you look at the assembly instructions.


Creating SEGFAULTs since 1995.


#5 Docom

Docom

    CC Lurker

  • New Member
  • Pip
  • 5 posts

Posted 18 March 2014 - 08:31 AM

you'd probably see more if you look at the assembly instructions.

 

Ah, there goes my quiet evening.

 

Thank you, gonerogue and Evan! Will redo the code with more pointers and less local variables.


Edited by Docom, 18 March 2014 - 08:31 AM.


#6 gonerogue

gonerogue

    CC Addict

  • Advanced Member
  • PipPipPipPipPip
  • 197 posts

Posted 18 March 2014 - 09:17 AM

It should be fairly easy to use a debugger to see what is happening.

I will detail how to do this using gdb on linux. But the same principles applies also when using other debuggers.

 

1. Assuming that the source code is in a file called test.c, compile the source code on O0 and using the -g flag (to generate debug information).

 

In terminal run:

gcc -g -O0 test.c -o test -> this produces an executable called test

 

2. Double check that the issue reproduces:

In terminal run:

./test

an inspect the output

 

3. Assuming that the issue reproduces, start the gdb debugger by running in terminal:

gdb -tui ./test  -> is easier to use the tui mode

 

4. Set a breakpoint at this line:

listPtr->next = &newItem;

Run this from the gdb console:

(gdb) break test.c:14  --> 14 is the line number of the source line above.

 

3. Than run the program.

(gdb) run

The program execution will stop after hitting the breakpoint.

Double check, in the source view, that you are at line 14.

 

4. Find out the address of the newItem local variable

(gdb) print &newItem

 

gdb will reply with something like this:

$1 = (struct listItem *) 0xbffff518

In my case, the address is 0xbffff518. In your case most likely will be a different address.

 

5. Now comes the interesting part.

Set a write watchpoint at the address found out at step 4.

(gdb) watch *(struct listItem *) 0xbffff518 --> update this for your address.
 
gdb will reply with something like this
Hardware watchpoint 2: *(struct listItem *) 0xbffff518
signaling that the watchpoint was set.
 
6. Continue the execution of the program
If the program will write to the address where you set the watchpoint, the execution will stop after the write access. This way you can find out exactly who is writing at that address.
 
(gdb) continue
 
gdb will reply when stopping with the current value of the EIP register (of course, an address).
In the source code you will also see which source code corresponds to that address.
 
You can double check that the returned address matches the EIP register content:
(gdb) info registers eip
 
So, all you have to do is to inspect the code just, knowing that you have stopped just after the content of the address (where the watchpoint was set) was written.
 
You can switch to disassembly in gdb and inspect the dissasembly:
(gdb) layout asm
 
If you want to disassemble the test executable outside gdb, you can use objdump:
objdump -d ./test

Edited by gonerogue, 18 March 2014 - 09:21 AM.


#7 0xDEADBEEF

0xDEADBEEF

    CC Devotee

  • Senior Member
  • PipPipPipPipPipPip
  • 790 posts

Posted 18 March 2014 - 09:20 AM

Good advice there.

 

But as I said it would be your machine specific (well your compiler/os/arch specific).

 

As an example I ran through your code on a solaris box, using gcc 3.4.3. An it worked with the printf and without.


Creating SEGFAULTs since 1995.


#8 gonerogue

gonerogue

    CC Addict

  • Advanced Member
  • PipPipPipPipPip
  • 197 posts

Posted 18 March 2014 - 09:28 AM

The original posted code worked on my linux system too, but it was fairly easy to modify the code so that the memory content at the local address gets overwritten (on my system):

#include <stdio.h>
 
// define linked list containing integers
struct listItem
{
    int value;
    struct listItem *next;
};
 
// insert into linked list
inline void insertItem (int newValue, struct listItem *listPtr)
{
    struct listItem newItem = {newValue, listPtr->next};
    listPtr->next = &newItem;
    printf("\n"); // This is the problematic printf()
}
 
// useless function used only to reproduce the issue in the function above ...
int foo(void)
{
    volatile int x = 999, y = 998, t = 997, u = 996, z;
    z = x + y + t + u;
    return z;
}
 
int main (void)
{
    struct listItem firstItem = {1};
    struct listItem *listPtr = &firstItem;
 
    insertItem(2, listPtr);
    foo();
 
    printf("Values:\n%d\n%d\n\n", listPtr->value, listPtr->next->value);
    printf("Addresses:\n%x\n%x\n", (int) listPtr, (int) listPtr->next);
    return 0;
}


#9 Docom

Docom

    CC Lurker

  • New Member
  • Pip
  • 5 posts

Posted 18 March 2014 - 10:07 AM

The first thing I noticed following the above is that the level of optimisation can make or break this bit of code - with levels 2-3 it broke, while levels 0-1 did not affect it. I assume that in some way the printf() forces the frame to stay for a while following the exit from insertItem(), which is lost by both setting higher levels of optimisation, as well as variable assignment, in either case getting that memory address rewritten.

 

Also: First use of gdb. Interesting, though I think I'll prefer Eclipse's GUI. As for the execution - I'm using TDM-GCC so I have gdb on Windows too, only without the TUI, which is curses-dependant.

 

Thanks to you both!



#10 gonerogue

gonerogue

    CC Addict

  • Advanced Member
  • PipPipPipPipPip
  • 197 posts

Posted 18 March 2014 - 10:07 AM

As an exemplification of the above method, let us do the steps for the code I have posted above (at post 8).

The address of the newItem variable, local in function insertItem(), is 0xbffff518 (on my system).

 

Let us try to find out which is the code which writes at this address after exiting from the function insertItem()

 

Steps 1-5 are exactly as described above (in post 6).

 

At step 6, after executing the continue command, on my system gdb replies as follows:

 

(gdb) continue
Continuing.
Hardware watchpoint 2: *(struct listItem *) 0xbffff518
 
Old value = {value = 2, next = 0x0}
New value = {value = 2, next = 0x3e5}
0x0804843b in foo () at test.c:20
 
So it seems that we stopped at address 0x0804843b after hitting the watchpoint set at address 0xbffff518.
Let us doublecheck by inspecting the EIP regiser
(gdb) info register eip
eip            0x804843b        0x804843b <foo+27>
 
Looks right.
 
Let us look in the disassembly at the address pointed by EIP:
(gdb) disassemble
 
At the address pointed by EIP we have:
0x804843b <foo+27>      movl   $0x3e4,-0x10(%ebp)
 
It looks like this is the instruction which caused the write access. Let us see what is doing.
It writes the value 0x3e4 (996 in decimal) at a memory area located on stack, at offset -0x10 from EBP.
Let us calculate that address.
First, let us see what is the value in the EBP register:
(gdb) info registers ebp
ebp            0xbffff528       0xbffff528
 
So the write is made at 0xbffff528 - 0x10 = 0xbffff518, which was the address of newItem.
By inspecting the source code, wee can see that the instruction movl   $0x3e4,-0x10(%ebp) corresponds to the assignment u = 996, from the function foo().
Let us double check that u is at address 0xbffff518:
(gdb) print &u
$2 = (volatile int *) 0xbffff518
 
So, by using easy debugging techniques, we managed to find out which code wrote at our address.
It was the assignment u = 996
 
Let us run the program. The output on my system is:
 
Values:
1
996  --> this makes a lot of sense after the analysis we have made.
 
Addresses:
bff71ab4
bff71a88
 
Personally I don't like Eclipse for C/C++ developing ... for example, I don't like the debugger integration (I even contributed at the development of the Eclipse CDT project - at work we use it for a comercial product ... but I still don't like it at all ...).
 
 

Edited by gonerogue, 18 March 2014 - 10:33 AM.


#11 Docom

Docom

    CC Lurker

  • New Member
  • Pip
  • 5 posts

Posted 18 March 2014 - 10:42 AM

Interesting. Debuggers 101 (Python is usually happy just using "print", but well - a different category of programming language).

 

Re: Eclipse: I'm just much less of a fan of Unix/Linux commandline programs. They have their uses, but overall GUIs... well, that's for a different thread!



#12 0xDEADBEEF

0xDEADBEEF

    CC Devotee

  • Senior Member
  • PipPipPipPipPipPip
  • 790 posts

Posted 18 March 2014 - 11:51 AM

Cool.

 

In the contrived example (with foo) its obvious why the memory is overwritten, you call another function which overwrites the stack.

 

You say that with the different optimisation levels you get different results which makes sense - optimisations play with passing of parameters and returns among many many others things.


Creating SEGFAULTs since 1995.





Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download