1

I am making my second read trough CPP institute CLA course materials and I am still struggling with pointers. Here is the code snippet that I am trying to understand. I commented my logic in the code below.

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


int main(void) {
//      we are dereferencing pointer t of type float
//      to = integer literal 1 + float pointer to memory location size 
//      of 2xfloats.
//      I am guessing that this memory location will be filled with 
//      all zeroes or this assignment is invalid.
//      This initial assignment is completely confusing me
//      and rest of my logic/reading this snippet is probably wrong :D

float *t = 1 + (float *) malloc(sizeof(float) * sizeof(float));
printf("%f\n",*t);      //0.00000

t--;                    //we move  pointer to next zero in memory?!
printf("%f\n",*t);      //0.00000

*t = 8.0;               //we dereference pointer t and assign literal 
                        //8.0. This will be [0] position
printf("%f\n",*t);      //8.00000

t[1] = *t / 4.0;        //we assign index position [1] to (*t (which 
                        //is previously assigned to 8.0) divided by 
                        //4.0 . This will assign t[1] to 2)
printf("%f\n",*t);      //2.00000

t++;                    //we move pointer to next position [1] = 2
printf("%f\n",*t);      //2.00000

t[-1] = *t / 2.0;       //moving to position [0] and assigning it to 1
printf("%f\n",*t);      //2.00000
free(--t);
return 0;
}

Apologies I didn't mention that this is not part of a working program. It is a long list of short "trick" snippets to check understanding of the material. I will go through detailed answers you provided. And Thank you all :D

Peja
  • 13
  • 3

3 Answers3

2

<gratuitous rant>

If this example is representative of the overall quality of the course materials available from the CPP Institute, then I very strongly suggest you look elsewhere for your education and/or certification.

Unless this example is titled "How To NOT Write Code That Uses Pointers" (in which case it is very effective), this code is awful. Not only is it confusing, it's chock full of bad practice and undefined behavior.

</gratuitous rant>

Let's start with the obvious problems:

float *t = 1 + (float *) malloc(sizeof(float) * sizeof(float));

This - I don't know what this is supposed to illustrate, except how to write really confusing code.

sizeof (float) evaluates to the number of bytes in a float, which on most modern platforms will be 4 bytes. Squaring that value means that you're setting aside enough space for N objects where each object is N bytes wide - IOW, if sizeof (float) is 4, you get enough space for 4 objects, whereas if sizeof (float) is 8, you get enough space for 8 objects.

That's an ... unusual way of specifying how many objects you want. Us boring people in the real world just write sizeof (float) * N, where N is the number of objects we want (actually, we write sizeof *t * N).

Okay, so we allocate enough space for N objects and return the pointer - plus 1? Pointer arithmetic takes the size of the pointed-to type into account, so adding 1 to a pointer means "point to the next object of the pointed-to type" (the array subscript operator a[i] is defined as *(a + i) - given a starting address a, compute the address of the i'th object following a and dereference the result).

So basically, t starts out pointing to the second object in the dynamic buffer. Drawing it out, you get something like this:

      +---+
      |   |
      +---+
t ->  |   |
      +---+
      |   |
      +---+
      |   |
      +---+

malloc does not initialize the dynamic buffer - the contents of the buffer are indeterminate. There's no guarantee that the bit pattern resident in that memory area corresponds to a value of 0.00. Since the object is uninitialized, attempting to access its value results in undefined behavior - the line

printf("%f\n", *t);

may result in output of 0.0000, or it may result in some other random value, or you may get a runtime error, or...

The statement

t--;

subtracts 1 from the pointer, causing it to point to the first element in the buffer:

      +---+
t ->  |   |
      +---+
      |   |
      +---+
      |   |
      +---+
      |   |
      +---+

*t = 8.0;               //we dereference pointer t and assign literal 
                        //8.0. This will be [0] position
printf("%f\n",*t);      //8.00000

Correct, although it would be a bit clearer to write

t[0] = 8.0;
printf("%f\n", t[0]);

When dealing with something you're treating as an array, it's best to use array subscript notation.

t[1] = *t / 4.0;

This code hurts. Seriously, mixing array and pointer notation is guaranteed to cause heartburn. This would better have been written

t[1] = t[0] / 4.0;

The statement

t++;

adds 1 to the pointer, getting us back to the previous state where we're pointing at the second element of the array.

t[-1] = *t / 2.0;

And this code merits a paddlin'. Negative indices are bad juju. Since t is pointing to the second element of the array now, this won't blow up, but it's just ... just ... don't do that. If anyone turned code like that into me for review, I'd kick it back so hard they'd feel it for a week. Seriously, don't do that.

Here's how that code should have been written:

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

#define N 2 // assuming you only want 2 floats.

int main(void) {
  /**
   * Since the type of t is float *, the *expression* *t has
   * type float - thus, sizeof *t == sizeof (float).  Oh, and
   * the cast on malloc is unnecessary, and under C89 will suppress
   * a useful diagnostic if you forget to include stdlib.h.  
   */
  float *t = malloc( sizeof *t * N ); 

  if ( t ) // ***ALWAYS*** check the result of malloc, calloc, or realloc
  {
    t[0] = 8.0; 
    printf( "%f\n", t[0] );      // 8.000

    t[1] = t[0] / 4.0;
    printf( "%f\n", t[1] );      //2.00000

    t[0] = t[1] / 2.0;         
    printf( "%f\n", t[0]);

    free(t);                     // release the memory
  }
  return 0;
}

Note that you can use calloc instead of realloc if you want the allocated memory to be initialized to all-bits-0:

float *t = calloc( N, sizeof *t );

Just be aware that all-bits-0 doesn't necessarily correspond to 0.000.

John Bode
  • 119,563
  • 19
  • 122
  • 198
  • Thank you. My eyes bugged out at `sizeof(float) * sizeof(float)`. Wouldn't it be fun if `sizeof(float)` were `1`? – John Kugelman Apr 19 '18 at 21:43
  • @JohnKugelman: The `1 +` on that line was what got me - damn near choked on my coffee when I saw that and understood what they were doing. – John Bode Apr 19 '18 at 21:44
  • Thanks for an explanation this helped and cleared my bad understanding of what allocating memory does. And once more to mention that this "trick" snippet is from a long list of snippets that is testing your understanding of the material. All of them are super weird and make me cry. But I will remember what NOT to do in real world :D – Peja Apr 20 '18 at 10:40
  • @Peja: Yeah, I just *hate* these kinds of examples. There are ways of testing your knowledge without resorting to "tricky" code that has no relationship to real-world code. Not there isn't some *god awful* real-world code out there, it's just awful in a different way. – John Bode Apr 20 '18 at 15:07
1

Almost all your assumptions seem to be correct; Only the first one "I am guessing that this memory location will be filled with all zeroes or this assignment is invalid" could be a little bit more concrete:

float *t = 1 + (float *) malloc(sizeof(float) * sizeof(float));

allocates memory for several float values; actually sizeof(float) * sizeof(float) rarely makes sense as the number of float values available depends on the size of a float. It does not initialize the memory with 0, such that the memory is not initialized. And - that's the main thing now - accessing uninitialized values as you do with printf("%f\n",*t); yields undefined behaviour. The compiler is allowed to do anything, even completely ignore the statement.

So you'd actually write

float *t = 1 + (float *) calloc(sizeof(float), 4);

calloc initializes the memory to 0, and the number of floats is a bit more deterministic.

Stephan Lechner
  • 34,891
  • 4
  • 35
  • 58
  • Theoretically the `calloc` is insufficient as the system might not be following IEC 60559 – M.M Apr 19 '18 at 23:43
0

The first printf("%f\n",*t); causes undefined behaviour by reading an uninitialized memory location and passing the result to a standard library function.

As explained in the link, this means the entire output of the program is meaningless. There is no point even reading the code beyond this line.

M.M
  • 138,810
  • 21
  • 208
  • 365