Page 1 of 1

C program, question about pointer arguments

Posted: Mon Oct 11, 2004 10:29 pm
by chen17981
Now I have some question about pointer arguments.
Following is my code, my complier is GCC.

Code: Select all

#include <stdio.h>
void fn(char *a)
{
  a[0] = 'a';
  a[1] = 'b';
  a[2] = '\0';
}

int main()
{       
    char* a;
    int i = 0;
    fn(a);
    printf("*** a = %s ***\n",a);
    
    for(;i<3;i++)
    {
      if(a[i] != '\0')   
      printf("*** a[%d] = %c ***\n",i,a[i]);
    }    
    return 0;   
}
the output is that: **** a = (null) *****
*** a[0] = a ***
*** a[1] = b ***

So I do not understand why the two results are different.
why i cannot get any result with %s, but could get a expected result with %c. Although I know that my code would result in memory leak.

Hope someone could give some explanation. Thanks in advance.

Re:C program, question about pointer arguments

Posted: Mon Oct 11, 2004 11:23 pm
by Candy
chen17981 wrote: So I do not understand why the two results are different.
why i cannot get any result with %s, but could get a expected result with %c. Although I know that my code would result in memory leak.

Hope someone could give some explanation. Thanks in advance.
You are testing an undefined situation for a certain outcome. Don't do that.

You didn't initialize the pointer. The printf routine has a method to test whether you're printing out a null pointer, so it prints (null) instead. If you manually dereference it (in a situation that allows, such as an OS) you get something else.

Never use uninitialized pointers.

Re:C program, question about pointer arguments

Posted: Wed Oct 13, 2004 2:29 am
by Schol-R-LEA
Actually, thre are two related, but subtly different, errors involved. One is that *a in main() isn't initialized, and thus is pointing to random space (it is only set to null in this case because the stack is initially cleared when the program starts; had this been a function which was called after some other function, rather than in main(), it might have ended up pointing anywhere). The second is that the memory it is pointing to has not been allocated as char array, and thus may actually point to other data (which could cause it to be overwritten) or even code (which would probably cause a memory protection violation).

Because char pointers are often used to point to c-strings (which are really just char arrays with a nul-character marking the end of the data), there is a tendency to forget that they are not strings as you would see in other languages; most notably, c-strings have to properly allocated and deallocated, either statically at declaration time, or dynamically using malloc() (or calloc()) and free().

In this case, it's probably easiest to replace [tt]char *a;[/tt] with [tt]char a[3];[/tt]; it would behave pretty much as you would expect it to. The alternative is to use [tt]a = calloc(sizeof(char), 3);[/tt] before the call to fn(), and then add [tt]free(a);[/tt] just before the [tt]return[/tt] statement.

BTW, don't ever try to free() a statically allocated array; at best, it would return a compiler error.

Re:C program, question about pointer arguments

Posted: Thu Oct 14, 2004 12:19 am
by zloba
offtopic, i'll pick on the details:
don't ever try to free() a statically allocated array; at best, it would return a compiler error.
compiler error - on what grounds? is free(void *) somehow so special that compiler checks its semantics for every weird possibility? i don't think so.

the compiler has nothing to complain about. you feed it a pointer and it will dutifully try to free it and will either crash or glitch or do nothing...

it's a slightly different story with C++, since new/delete are indeed special:

Code: Select all

free.cpp: In function `int main()':
free.cpp:3: warning: deleting array `char buf[100]'

Re:C program, question about pointer arguments

Posted: Thu Oct 14, 2004 5:04 pm
by Schol-R-LEA
Urk. You're right; when I complie

Code: Select all

#include <stdlib.h>

int a[10];

main()
{
    char b[10];
    
    free(a);
    free(b);
} 
in gcc, the compiler doesn't give any warning, even with full checking on.

The lack of compiler warnings is all the more reason to be careful to avoid it. Such a bug would probably be quite difficult to track down.

Re:C program, question about pointer arguments

Posted: Thu Oct 14, 2004 5:41 pm
by mystran
While checking that specific example would be quite easy, checking free's of permanent or stack allocated storage in general is one of those things that calls for the infamous "sufficiently smart compiler". You'd have to trace where such pointers are passed, and then check if it is possible that they might be freed. But you can't complain simply because they "might" be freed, unless you can prove that such a state is also reached. In presence of separate compilation that is simply impossible, but even when compiling the whole thing at once (which can't ever happens, unless you are creating a freestanding kernel), the problem might require enumerating all possible states of the program... which naturally is impossible in practice, for anything more than the most simple programs.

The ability to cast types to other types, so as to by-pass the typing system doesn't exactly help the problem either.

As such, I believe gcc follows the principle that if catching things like this is more of an exception than a rule, then it's bad idea to warn user about it, because it might make them think that the compiler will catch such mistakes.

Runtime checks on the other hand are quite trivial for this particular case: simply check if the address given is within the heap.

Re:C program, question about pointer arguments

Posted: Thu Oct 14, 2004 9:09 pm
by zloba
a rather useless hack can detect the most obvious cases:

Code: Select all

#include <stdio.h>
#include <malloc.h>
#define myfree(x) { if(sizeof(x)!=sizeof(void *))printf("baka\n"); else free(x); }

int main(){
    char buf[100];
    myfree(buf);
    return 0;
};

Code: Select all

$ ./free 
baka

Re:C program, question about pointer arguments

Posted: Fri Oct 15, 2004 1:06 pm
by mystran
If you really want to catch those, a better idea might be to do something like:

Code: Select all

#include <stdio.h>
/* This symbol is inserted at the end of .bss section by gcc
 * default linker script. For obvious reasons, relying on such
 * a symbol is NOT PORTABLE AT ALL.
 *
 * Type doesn't matter since we only take the address anyway.
 */
extern int _end;
void myfree(void * p) {
    int foo;
    if(p > &foo||p < &_end) {
        fprintf(stderr, "myfree: pointer not in heap.\n");
        abort();
    }
    free(p);
}
Like said in the comment, it's not portable to assume that there's an _end symbol in there. On my x86 linux with gcc though (and probably other gcc targets), this is the case. To verify that this is indeed the case, you can look at the default linker scripts.

But that's not the only problem. We also rely on the fact that heap is after the mapped image, that is, any address in the heap is necessarily after _end.

Finally, we check that it's not a stack allocated pointer. We do this by comparing the address with the address of a local variable. This relies on the fact that stack grows down (towards address zero) on x86. Since the local variable is (almost) on top of the stack, any valid address within the stack is necessarily after it.

Ofcourse if you take the address of a local in some function, then return from that function (so that the stack address is no longer valid) we won't necessarily catch that. Doing so is a bug anyway, and since catching it would require us to know where the heap ends, I didn't bother.

Finding the end of heap isn't necessarily hard though. Just write another function mymalloc(), which calls malloc, and if we get a block which extends after the known end of heap (add the address and the blocksize), write it down as the new known heap end. The ugly thing is that then you need to make sure malloc() is never called directly, so I think doing this is even worse idea than using the current stack-top.

Re:C program, question about pointer arguments

Posted: Fri Oct 22, 2004 5:29 am
by Solar
mystran wrote: If you really want to catch those, a better idea might be to do something like...
It's not only less than portable, it also fails horribly on any system not having virtual memory. Like an x86 real-mode OS or an Amiga. :D

Re:C program, question about pointer arguments

Posted: Fri Oct 22, 2004 9:16 pm
by mystran
To repeat:
- it relies on code first, heap then, stack last.
- the previous implies flat address space, which is relied on
- the first also implies there's only one stack, and it's ouside heap
- it assumes stack grows down (toward memory address 0)
- it assumes x86 C calling convention, and that locals are allocated in stack.
- it assumes you have a _end linker symbol.

It probably assume other things as well, but anyway, once you fix the above to match your architecture (where possible at all) you probably touch the other issues as well.

Anyway, this also serves as a good example of how to write NON-portable C code. You can write a garbage collector in pure C after all, but you can't do that in a portable manner.

You can get rid of most of the issues (including _end, stack direction and local allocation policy) by writing a malloc() wrapper that keeps the first and the last heap address in memory somewhere, and use those values instead, like I said, but not assuming flat address space makes the code order's of magnitude more complex. Assuming heap is one continuous piece of memory (with code and stack as separate parts) is also something that's quite hard to get rid of, without making the code lot more complex.