2

The following is a simple code:

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

void main(void)
{
   int i,n=10;
   double *a;
   a = (double *)calloc(n,sizeof(double));
   if (!a){
       printf("Allocating error!\n");
       exit(1);
   }
   for (i=0;i<10;i++){
       printf("%f\n",*a++); /*Print 1*/
       printf("%f\n",a++);  /*Print 2*/
       printf("%f\n",a[i]); /*Print 3*/
   }
}

Note: the three print lines were tested separately, for instance, run printf("%f\n",*a++); /*Print 1*/ and let the other two print lines as comment lines.

My question is: why three different print lines have the same output? My understanding is Print 2 and 3 have the same meaning, but they're address... Print 1 has a dereference sign (*) so it's the number in double-type. I'm confused, can anybody give a clear explanation?

Attach compilation message:

warning: format ‘%f’ expects argument of type ‘double’, but argument 2 has type ‘double *’ [-Wformat=]
        printf("%f\n",a++);  /*Print 2*/

But the weird thing is: if I directly execute the code rather than debug it first, there's no mistake and all of them output the correct results (ten 0.000000).

Thomas D.
  • 87
  • 8

3 Answers3

0

Firstly compile your program with warnings enabled flags as below, it may tell you number of things.

gcc -g -O -Wall test.c

Here is the explanation .

int  main()
{
        int i,n=10;
        double *a;
        a = (double *)calloc(n,sizeof(*a));/* use sizeof(*a) instead of sizeof(double) */
        if (!a){
                printf("Allocating error!\n");
                exit(1);
        }
        for (i=0;i<10;i++)
                printf("%lf\n",*a++); /* use %lf as a is of double ptr type
                                        it prints 0.00 everytimes not 1 as calloc zeroed 
                                        whole memory */

        /* when loop fails where a points ? */
        printf("%p\n",a++);  /* it prints address and use %p instead of %f */
        printf("%lf\n",a[i]); /* it results in undefined behaviour 
                                as you are trying to access out of boundry like a[11] 
                                which you didn't allocated */

        return 0;
}
Achal
  • 11,821
  • 2
  • 15
  • 37
  • `%f` is fine for formatting a `double` argument in `printf`. It is defined to take a `double` value. – Eric Postpischil Mar 24 '18 at 03:09
  • but if usage of `%f` with `*a` will print only data in first `4 byte(GCC)` , correct me if I'm wrong. – Achal Mar 24 '18 at 03:21
  • Sorry, forgot to mention, the three print lines were tested separately, for instance, run `printf("%f\n",*a++); /*Print 1*/` and let the other two print lines as comment lines. – Thomas D. Mar 24 '18 at 04:02
  • 1
    @achal: You are quite wrong. In `printf("%f\n",*a++)`, `a` points to a double. The pointer is incremented, and the initially pointed-to `double` is retrieved. That `double` is passed to `printf`. The `f` conversion specifier fot `printf` is **defined in the C standard** to take a `double` argument. It does so and converts the `double` value. (`scanf` is defined to take a pointer to `float` for `%f` and a pointer to `double` for `%lf`. For `printf`, they are both `double`.) – Eric Postpischil Mar 24 '18 at 08:04
  • Thanks @EricPostpischil I thought for both `printf` and `scanf` it works as same. this is very informative. I corrected myself. – Achal Mar 24 '18 at 08:09
0

You invoke Undefined Behavior by attempting to print double* as double on each iteration with your call printf("%f\n",a++); and then again by attempting to access values beyond the end of your allocated block of memory on your call to printf (" %f\n", a[i]); beginning when i = 3 and for each subsequent iteration of your for loop.

Why are weird things happening?

When you declare double *a;, a is a pointer to double, not a double itself. What is a pointer? A pointer is simply a variable that holds the address of something else as its value. When you attempt to call printf("%f\n",a++); you are attempting to print the pointer, not the value at the address held by the pointer (which you obtain by dereferencing the pointer with the unary '*' operator, e.g. *a).

This is undefined behavior as described in C11 §7.21.6.1(p2 & p9) The fprintf function - insufficient arguments, or incorrect type.

Next, your attempt to both loop over each element with a for loop while incrementing the pointer within the loop causes you to attempt to read values beyond the end of your allocated block beginning with i = 3.

When you allocate memory for a with a = calloc (n, sizeof(double)); (note: See: Do I cast the result of malloc?), you allocate n times sizeof(double) bytes initialized to all zero by using calloc. (80-bytes on most modern desktops). You allocate space for 10 doubles that can be access through indexes:

+---+---+---+---+---+-  /  -+---+---+
| 0 | 1 | 2 | 3 | 4 | ..\ ..| 8 | 9 |
+---+---+---+---+---+-  /  -+---+---+

One of the promises you make to the compiler when you allocate memory, is a promise you will only attempt to write or access values within the block, (you can reference the byte past the last value, but cannot attempt to read or write at that or any other address outside of your allocated block.

When you begin your loop, a holds the address to the beginning of the block of allocated memory, e.g.

+---+---+---+---+---+-  /  -+---+---+
| 0 | 1 | 2 | 3 | 4 | ..\ ..| 8 | 9 |
+---+---+---+---+---+-  /  -+---+---+
^
a

On your first call to printf("%f\n",*a++);, a side-effect of the post-increment operator (e.g. a++) is that the pointer a is advance by sizeof *a bytes (or one double). That is the entire basis of pointer arithmetic. So after your call to printf("%f\n",*a++);, a points to the second element, e.g.

+---+---+---+---+---+-  /  -+---+---+
| 0 | 1 | 2 | 3 | 4 | ..\ ..| 8 | 9 |
+---+---+---+---+---+-  /  -+---+---+
    ^
    a

Your next call to printf("%f\n",a++); invokes Undefined Behavior and then again advances the pointer by use of the post increment operator leaving a pointing to the third-element, e.g.

+---+---+---+---+---+-  /  -+---+---+
| 0 | 1 | 2 | 3 | 4 | ..\ ..| 8 | 9 |
+---+---+---+---+---+-  /  -+---+---+
        ^
        a

You then call printf("%f\n",a[i]); which prints the third-element, and you for loop increments i = 1, and then cycle repeats, except with each increment, when you call printf("%f\n",a[i]);, you reference the current value of a plus the index. Meaning a[i] is just array-index notation that is the equivalent of *(a + i) in pointer notation.

Each iteration within your for loop, you advance where a points by 16-bytes (or by two double values) by your use of a++ within the loop. On your 4th iteration (where i = 3), immediately prior to your call to printf("%f\n",a[i]);, a points to one-before the last element of your allocated block, e.g.

+---+---+---+---+---+-  /  -+---+---+---+---+---+---+---+---+
| 0 | 1 | 2 | 3 | 4 | ..\ ..| 8 | 9 |  you don't own this   |
+---+---+---+---+---+-  /  -+---+---+---+---+---+---+---+---+
                            ^           ^
                            a         a[3]

So when you attempt to print with a[3] (e.g. *(a + 3)) you are attempting to print a value from an address 8-bytes (or one double) past the end of your allocated block. By the end of the next iteration a no longer points anywhere within the your allocated block of memory, e.g.

+---+---+---+---+---+-  /  -+---+---+---+---+---+---+---+---+
| 0 | 1 | 2 | 3 | 4 | ..\ ..| 8 | 9 |  you don't own this   |
+---+---+---+---+---+-  /  -+---+---+---+---+---+---+---+---+
                                    ^
                                    a

Further, you have lost the ability to free(a), because you have failed to preserve a pointer to the beginning of the block of memory you have allocated. If you allocate a = calloc (n, sizeof(double));, never increment a itself without first saving a copy of the beginning address first. Generally you won't iterate a, but instead a second pointer, such as double *p = a;, then you can p++ all you like and still have the ability to free(a); because a still points to the beginning of the allocated block of memory. In your case, attempting to free(a) will likely cause an immediate crash.

When you invoke Undefined Behavior, the behavior of your code can do anything from appear to act correctly to SegFault'ing. (what it will do is undefined). It is ill-formed code. So don't break your promise with the compiler, don't attempt to read or write to address outside the allocated block of memory.

Lastly, the proper declarations for main are int main (void) and int main (int argc, char **argv) (which you will see written with the equivalent char *argv[]). note: main is a function of type int and it returns a value. See: C11 Standard §5.1.2.2.1 Program startup p1 (draft n1570). See also: See What should main() return in C and C++?. void main is an incorrect anachronism in virtually all systems, and all standard compliant systems.

Look things over and let me know if you have further questions.

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
  • As stated in the question, the executed code contains only one of the displayed `printf` statements at a time. There is no overrun of the allocated storage. The only error is passing a pointer for `%f`. This statement was edited into the question over two hours before you answered. – Eric Postpischil Mar 24 '18 at 08:11
0

As others have noted, passing a pointer when using the %f conversion specifier for printf has behavior not defined by the C standard.

The reason it printed zero in your test is likely because the register where a double argument should have been passed contained zero. Many processors have separate registers for integer values and for floating-point values. When printf is printing a double value, it takes the value from the register designated for passing a floating-point argument. Since you passed a pointer, its value was put into the register designated for passing an integer-type argument, and the floating-point register was not changed. Since your program did not contain any actual floating-point operations, that register may have not been used at all in the program, so it still contained the zero that the operating system initialized it to when starting your process. That zero was printed.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312