1

I've got a peice of code which takes an an array by address (pointer) This function simply takes an array and an integer value which is its length, and prints out the elements of the array one by one.

void dumpInts(int *array, int count, int hex){
    int i;    
    for(i = 0; i < count; i++){
        printf("  %*d",10,array[i]);
    } 

The above code is highly simplified, Here is the rest of the code. Its not that large.

#include <stdio.h>
#include <stdlib.h>
int LAST = -1059786739;
int FIRST = -559038737;
//Input is a series of integers read in from stdin separated by whitespace
void dumpInts(int *array, int count, int hex);
void initArray(int *array, int size);

int main(){
    int bufsiz ;
    if(scanf("%d",&bufsiz) < 1){
        fprintf(stderr,"CAN\'T READ BUFSIZ\n");
        return 1;
    }
    if(bufsiz < 1){
        fprintf(stderr,"BAD BUFSIZ=%d\n",bufsiz);
        return 1;
    }
    bufsiz+=2;
    int *array = (int *)malloc((size_t)bufsiz);
    if(array == NULL){
        fprintf(stderr,"NO MORE MEM\n");
        return 1;
    }
    array[0] = FIRST;        
    array[bufsiz-1] = LAST;   
    initArray(array,bufsiz);
    dumpInts(array,bufsiz,0);
    dumpInts(array,bufsiz,1);
    return 0;
}

void initArray(int *array, int size){
    int i;
    for(i = 1; i < size - 1; i++){
        array[i] = i;
    }
}

void dumpInts(int *array, int count, int hex){
    int i;    
    for(i = 0; i < count; i++){
        printf("  %*d",10,array[i]);
} 

Here's the thing. When The size of the array is under 7, it works great. Check out this snippet from my terminal:

-559038737 1 2 3 4 -1059786739

Suddenly though, when the size surpasses 7, the last element gets overwritten and all of the following memory is set to random values, but the 7'th element is always set to the same thing... 1041

Below is an array of size 8

-559038737 1 2 3 4 5 1041 0 892677408 942878777 Now Before I go any further, I feel I should exert that I did spend time carefully narrowing down EXACTLY WHERE the array is clobbered. Before it enters the function it is what I expect it to be, as soon as the first loop prints, the array is clobbered.

Okay so this is where it gets weird. I SSHed into my university's linux machine, and ran my code. No clobber, the array is just fine.

Comparison below for size 7

mine:

-559038737 1 2 3 4 5 1041

university:

-559038737 1 2 3 4 5 -1059786739

I am about to reboot my machine....

*Just rebooted, same problem persists... Same number too... Same size...

Also, when I compile the code on the university computer and copy it to my machine, array is clobbered still. Not so when I compile it on my machine.

Both my machine and the university machine are unix based systems, Running linux.

What the hell is going on here?!

This doesn't make any sense. I'm passing the address of the array in memory, so I don't expect the stack to have anything to do with this?

tgabb
  • 349
  • 3
  • 12
  • 1
    *I'm passing the address of the array in memory, so I don't expect the stack to have anything to do with this?* - If the actual elements are on the stack, then of course the stack could have something to do with it. Of course there's no way to tell right now. A [mcve] is key. Also try compiling with -Wall -Wextra and maybe using something like clang-tidy or cppcheck for an easy start. – chris Sep 22 '17 at 04:39
  • There's no way for someone reading your code to verify for example that you have actually allocated enough memory to hold the number of integers specified by count. That's a likely source of problems. – Eric J. Sep 22 '17 at 04:41
  • 1
    My crystal ball says the error is in the code you haven't shown. You may have completely corrupted memory before even calling that function, but we can't tell. – Retired Ninja Sep 22 '17 at 04:45
  • okay okay im working it! – tgabb Sep 22 '17 at 04:49
  • 1
    Can't say for sure whether it solves anything, but `malloc` takes bytes, not object count. – chris Sep 22 '17 at 05:02
  • The weird thing is that the bug dissapears when I upload the same code to a different machine and compile on there. I've tried 10's of castings for malloc. sitze_t, int, uint... Nothing seems to work besides compiling on another machine, and even when I do that the same problem occurs when I move the executable back to my machine – tgabb Sep 22 '17 at 05:03
  • 2
    You're allocating memory with malloc. You ask for the number of elements that you require, whereas you should be asking for the number of bytes you need. So, instead of `malloc((size_t)bufsiz)` you should be asking for `malloc((size_t)bufsiz * sizeof(*array) )` bytes (sizeof(*array) is the same as sizeof(int) - it's datatype.) By asking for the sizeof the first element, we avoid problems when maintaining the code at a later date by using a different datatype and forgetting to modify the element size in our malloc call. @chris already mentioned just this problem it seems. ;) – enhzflep Sep 22 '17 at 05:15
  • 1
    You allocate too little memory. Printf also allocates memory. Printf overwrites the memory it allocated because it's its right. On another machine it behaves differently because printf and or malloc are implemented differently. – Art Sep 22 '17 at 05:41
  • Avoid allocation error. Use `p = malloc(sizeof *p * n)` idiom --> `int *array = malloc(sizeof *array * bufsiz);` – chux - Reinstate Monica Sep 22 '17 at 12:20

2 Answers2

1

The diagnostic implied in your question Calling printf changes array passed by address is incorrect: you do not pass the array by address, but merely pass an array element by value and printf does not change the array. What you observe is the effect of undefined behavior.

There is some confusion between the number of elements and the size of the allocated array: you could use a more meaningful name for the number of elements of the array (count) as opposed to the size of the array in bytes, which is passed to malloc().

As coded, you do not allocate enough memory for the array, both the code that initializes this array and the code that prints it have undefined behavior because you access the array beyond its boundaries.

Furthermore, you do not use the hex argument to select between decimal and hexadecimal output.

Here is a corrected version:

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

int LAST = -1059786739;
int FIRST = -559038737;

//Input is a series of integers read in from stdin separated by whitespace
void dumpInts(const int *array, int count, int hex);
void initArray(int *array, int size, int start);

int main(void) {
    int count;
    if (scanf("%d", &count) != 1) {
        fprintf(stderr,"Cannot read the number of elements\n");
        return 1;
    }
    if (count < 1) {
        fprintf(stderr,"Invalid count=%d\n", count);
        return 1;
    }
    count += 2;
    int *array = malloc(sizeof(*array), count);
    if (array == NULL) {
        fprintf(stderr,"Out of memory\n");
        return 1;
    }
    array[0] = FIRST;        
    initArray(array + 1, count - 2, 1);
    array[count - 1] = LAST;   
    dumpInts(array, count, 0);
    dumpInts(array, count, 1);
    return 0;
}

void initArray(int *array, int count, int start) {
    for (int i = 0; i < count; i++) {
        array[i] = start + i;
    }
}

void dumpInts(count int *array, int count, int hex) {
    for (int i = 0; i < count; i++) {
        if (hex) {
            printf("  %10x", array[i]);
        } else {
            printf("  %10d", array[i]);
        }
    }
} 
chqrlie
  • 131,814
  • 10
  • 121
  • 189
  • So, although the variable `array` itself is an integer memory address which is the beginning of my array, passing `array` to the function passes the actual array? So you're saying if I passed the function `&array`, then I would be passing by address? – tgabb Sep 22 '17 at 16:06
  • @TylerGabb: No, you would be passing the address of the array if you passed `array` or `array + i`, but instead you pass `array[i]` which is the value of the element at index `i`, an `int` passed by value. `printf` has no clue where the value comes from. – chqrlie Sep 22 '17 at 16:43
0
int *array = (int *)malloc((size_t)bufsiz);

should be :

int *array = (int *)malloc((sizeof(int))*bufsiz);

Complete explanation:

void *malloc(size_t size);

is the prototype of malloc. as you know size_t is long unsigned integer.

but the typecasting is not the problem.

lets say I take input bufsize = 8; then: int *array = (int *)malloc((size_t)bufsiz);

becomes int *array = (int *)malloc(10);

which should actually be int *array = (int *)malloc(4*10);

because we want to store 10 integers & considering sizeof(int) = 4.

so the pointer points to 10 bytes instead of 40 bytes required..therefore there is not sufficient memory to store the integers.

This is an ArrayIndexOutOfBoundsException which does not exist in C.

C does not do bound checking i.e. it never gives this error. thus if you keep on incrementing the pointer it just simply goes into buffer of malloc (does required read/write operations) and the code does not segfault until it runs out of memory allocated to program by the system.

also You are not freeing memory; free(array) is missing.

EDIT: malloc does not take object count. It takes number of bytes which are to be allocated. You are using malloc as calloc.

Prost !

DevX
  • 342
  • 7
  • 16
  • 1
    [Don't cast the result of `malloc` in C](http://stackoverflow.com/q/605845/995714) – phuclv Sep 22 '17 at 12:22
  • 1
    no one recommends casting it in C. It can be used but gives no advantage. Only in C++ it's required – phuclv Sep 22 '17 at 16:27
  • Yes I agree with you but i have a very neutral stance on it :) – DevX Sep 22 '17 at 16:29
  • @DevX -- It is good to not be too religious on this issue; personally, I prefer not to cast, and if you use such (unnecessary) casts in your answers on SO, be prepared for some tomatoes from the cheap seats ;) It seems that the only reason to cast the result of `malloc()` et al. in C is to communicate intention in some cases; I have rarely felt the need for this. But, just today I was reusing some code, changing `SDL_Color *color = malloc(sizeof *color * numcolors);` to `RGBA *color = malloc(sizeof *color * numcolors);`; I appreciated having only 1 thing to change, and fewer chances to err. – ad absurdum Sep 22 '17 at 20:24
  • `size_t` is an `unsigned` integer type, but need not be `unsigned long`, and `int` may not be 4 bytes wide, but must be at least 2 bytes wide. – ad absurdum Sep 22 '17 at 20:27
  • @David Bowling I assumed the the OP was using gcc. (I just pur a size_t variable in printf and gave it an %d, so so that gcc showed that format type mismatch saying size_t is unsigned long. But yeah you are right. Its written in documentation that size_t must be at least 16 bits long. :) – DevX Sep 23 '17 at 02:19
  • @stackoverflow The only reason I typecast malloc is because my University deducts marks, If I don't do so in my assignments. – DevX Sep 23 '17 at 02:22
  • @DevX -- Hard to argue with the professors (about casting `malloc()`).... As I said, I prefer to avoid the cast here (and to cast only when absolutely necessary, i.e., almost never), but I am with you that tolerance is preferable to zealotry on this issue. Regarding `size_t`: this is often implemented as `unsigned long`, but the Standard only requires that `size_t` be wide enough to hold at least a maximum value of 65535, as you indicated in your comment. But in my earlier comment, the 2 byte minimum was in reference to `int`s, which (more precisely) must be at least 16 bits wide also. – ad absurdum Sep 23 '17 at 02:38
  • @David Bowling my bad :) I didn't know that int also had to be at least 2 bytes. I agree with the advantage of *not* casting malloc that you mentioned.. I'll keep it in mind while coding for my future projects ! Thanks ! – DevX Sep 23 '17 at 02:43