msmith1114 msmith1114 - 4 years ago 175
C Question

Some C Pointer Questions


int *x; Declares pointer which holds an ADDRESS to a location in memory
*x = 51; * is de-reference operator. I'm saving 51 at that address.
&x; & is address of operator, so &x would probably print some memory location


What I don't understand:


  1. What would happen with
    x = 51
    after declaring
    int *x;
    Would just a '51' be stored in the pointer, so it'd be pointing to some error "51" location?

  2. What happens if you say
    &x
    ? would it just print the memory location that the pointer is sitting in?

  3. A function declaration is
    function(int *x)
    but calling the function the form is
    function(&x)
    . Why?

  4. malloc()
    I dont understand "WHY" it works, I understand what it does however. Since in the C library definition it returns a "pointer" to the requested memory.

    x = (int *) malloc (sizeof (int));

    is putting a pointer in x......but x is a pointer already? So is it pointing to another pointer?

    I've also seen it written like this
    x = malloc(sizeof(int));

    .....whats the difference?



5: The
**
operator, I'd assume it means "pointer to a pointer"....but I guess maybe I don't understand when/why this would come into play?

Answer Source

What would happen with x = 51 after declaring int *x; Would just a '51' be stored in the pointer, so it'd be pointing to some error "51" location?

Essentially, yes. x is a variable like any other, and you can assign new values to it like any other. The expression 51 will be converted from type int to type int *, and the result will be assigned to x.

51 is most likely not a valid pointer value, so attempting to dereference it will likely lead to a runtime error. Valid pointer values are obtained by either applying the unary & operator to an lvalue1, by calling one of malloc, calloc, or realloc, or by using an array expression in some circumstances2.

What happens if you say &x ? would it just print the memory location that the pointer is sitting in?

The expression &x will evaluate to the location of the object x, and the type of the resulting value will be int ** (pointer to pointer to int).

A function declaration is function(int *x) but calling the function the form is function(&x). Why?

Because, for any object x of type T, the expression &x yields a value of type T *:

void foo( T *ptr ) // for any type T
{
  *ptr = new_value(); // write a new value to the object ptr
}                     // points to

void bar( void )
{
  T var;

  foo( &var ); // foo updates the value of var
}

In the code above,

 ptr == &var // T *
*ptr ==  var // T

This is true for any type, including pointer types. If we replace the type T with a pointer type P *, that code above becomes

void foo( P **ptr )
{
  *ptr = new_value(); // write a new value to the thing ptr points to
}

void bar( void )
{
  P *var;

  foo( &var ); // write a new value to var
}

This code is exactly the same as the code above - the only thing that has changed is the types:

  ptr == &var                // P **
 *ptr ==  var                // P *
**ptr == *var == some_object // P

malloc() I dont understand "WHY" it works, I understand what it does however. Since in the C library definition it returns a "pointer" to the requested memory.

x = (int *) malloc (sizeof (int)); is putting a pointer in x......but x is a pointer already? So is it pointing to another pointer?

No. x is an object that stores a pointer value; malloc is a function that returns a pointer value.

I've also seen it written like this x = malloc(sizeof(int)); .....whats the difference?

The (int *) is a cast expression - it tells the compiler to treat the result of malloc as a pointer to int. As of the 1989 version of the language, that cast is no longer necessary, and its use is discouraged. IMO, the proper way to write a malloc call is

T *p = malloc( sizeof *p * number_of_elements );

This works because malloc returns void *, which is a "generic" pointer type - a void * value can be converted to any other pointer type (and vice versa) without a cast3.

Prior to the 1989 standard, malloc returned char *, which did require an explicit cast to assign the result to a different pointer type.

5: The ** operator, I'd assume it means "pointer to a pointer"....but I guess maybe I don't understand when/why this would come into play?

Multiple indirection (pointers to pointers, pointers to pointers to pointers, etc.) is a common thing. I showed one example above, where a function needs to write a new value to a parameter of pointer type. It's rare to see more than two or three levels of indirection, though.

EDIT

Some actual code might help. Here's a small example that creates two variables, one an int, and the other an int *. We start out with the pointer initialized to NULL (a well-defined "nowhere" value, guaranteed to compare unequal to any valid pointer value), then we set it to point to another integer variable, then we set it to point to memory allocated by malloc. I've added calls to a utility I wrote to display the contents of each object:

#include <stdio.h>
#include <stdlib.h>

#include "dumper.h"

int main( void )
{
  int x = 10;

  /**
   * Start with ptr pointing "nowhere"
   */
  int *ptr = NULL;

  char *names[]  = {"x", "ptr", "unnamed"};
  void *addrs[]  = {&x, &ptr, NULL };
  size_t sizes[] = { sizeof x, sizeof ptr, 0 };

  printf( "ptr is currently NULL\n\n" );

  dumper( names, addrs, sizes, 2, stdout );

  /**
   * Set ptr to point to x
   */
  ptr = &x;  // int * = int *; use the & operator to obtain the location of x

  printf( "ptr currently points to x\n\n" );

  dumper( names, addrs, sizes, 2, stdout );

  ptr = malloc( sizeof *ptr * 10 );    // int * = void *; use malloc to set aside
  if ( ptr )                           // dynamic memory and obtain its location;
  {                                    // in C, you can assign a void * to an int * without a cast
    for ( size_t i = 0; i < 10; i++ )
      ptr[i] = (int) i;
  }

  addrs[2] = ptr;
  sizes[2] = sizeof *ptr * 10;

  printf( "ptr currently points to dynamically allocated memory\n\n" );

  dumper( names, addrs, sizes, 3, stdout );

  free( ptr );

  return 0;
}

When built and run on my system (SLES-10 on x86-64, gcc 4.1.2), I get the following output:

$ ./pointer_example
ptr is currently NULL

           Item        Address   00   01   02   03
           ----        -------   --   --   --   --
              x 0x7fff89aeb304   0a   00   00   00    ....

            ptr 0x7fff89aeb2f8   00   00   00   00    ....
                0x7fff89aeb2fc   00   00   00   00    ....

ptr currently points to x

           Item        Address   00   01   02   03
           ----        -------   --   --   --   --
              x 0x7fff89aeb304   0a   00   00   00    ....

            ptr 0x7fff89aeb2f8   04   b3   ae   89    ....
                0x7fff89aeb2fc   ff   7f   00   00    ....

ptr currently points to dynamically allocated memory

           Item        Address   00   01   02   03
           ----        -------   --   --   --   --
              x 0x7fff89aeb304   0a   00   00   00    ....

            ptr 0x7fff89aeb2f8   10   20   50   00    ..P.
                0x7fff89aeb2fc   00   00   00   00    ....

        unnamed       0x502010   00   00   00   00    ....
                      0x502014   01   00   00   00    ....
                      0x502018   02   00   00   00    ....
                      0x50201c   03   00   00   00    ....
                      0x502020   04   00   00   00    ....
                      0x502024   05   00   00   00    ....
                      0x502028   06   00   00   00    ....
                      0x50202c   07   00   00   00    ....
                      0x502030   08   00   00   00    ....
                      0x502034   09   00   00   00    ....

Before going over that in detail, remember that x86-64 is little-endian, so multi-byte objects are stored starting with the least-significant bit. This also means that objects that span multiple 32-bit words are stored starting with the least-significant word.

In short, read the memory dumps for each object from right to left, bottom to top.

Starting with the first section:

           Item        Address   00   01   02   03
           ----        -------   --   --   --   --
              x 0x7fff89aeb304   0a   00   00   00    ....

            ptr 0x7fff89aeb2f8   00   00   00   00    ....
                0x7fff89aeb2fc   00   00   00   00    ....

The object x is stored starting at location 0x7fff89aeb304; on my system, objects of type int take up 4 bytes. The value stored at x is 10 (0x0000000a; again, read the memory dump from right to left).

The object ptr is stored starting at location 0x7fff89aeb2f8; on my system, objects of type int * take up 8 bytes4. The value stored at ptr is NULL (0).

After setting ptr to point to x, we get the following:

           Item        Address   00   01   02   03
           ----        -------   --   --   --   --
              x 0x7fff89aeb304   0a   00   00   00    ....

            ptr 0x7fff89aeb2f8   04   b3   ae   89    ....
                0x7fff89aeb2fc   ff   7f   00   00    ....

The value stored at ptr is now 0x7fff89aeb304, which is the address of x. Right now, the expressions x and *ptr would yield the same value (10).

Finally, we allocate space for 10 integers using malloc and set ptr to point to the first element in that sequence:

           Item        Address   00   01   02   03
           ----        -------   --   --   --   --
              x 0x7fff89aeb304   0a   00   00   00    ....

            ptr 0x7fff89aeb2f8   10   20   50   00    ..P.
                0x7fff89aeb2fc   00   00   00   00    ....

        unnamed       0x502010   00   00   00   00    ....
                      0x502014   01   00   00   00    ....
                      0x502018   02   00   00   00    ....
                      0x50201c   03   00   00   00    ....
                      0x502020   04   00   00   00    ....
                      0x502024   05   00   00   00    ....
                      0x502028   06   00   00   00    ....
                      0x50202c   07   00   00   00    ....
                      0x502030   08   00   00   00    ....
                      0x502034   09   00   00   00    ....

Hopefully, the pattern should be obvious by now - ptr stores the value 0x502010, which is the address of the first element of the dynamically-allocated sequence.


  1. An lvalue is an expression that refers to a region of memory such that the memory may be read or written to. A variable is an lvalue, but other expressions may be lvalues as well.
  2. Except when it is the operand of the sizeof or the unary &, or is a string literal used to initialize a character array in a declaraction, an expression of type "N-element array of T" will be converted ("decay") to an expression of type "pointer to T", and the value of the expression will be the address of the first element of the array.
  3. This is not the case in C++, however; a call to malloc in C++ does require a cast, but you shouldn't use malloc in C++ code anyway.
  4. Pointers to different types *may* have different sizes and representations, although I think x86-64 uses 8 bytes for all pointer types.

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