Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Global variable losing data in C?

Tags:

c

I am trying to create a array of array of string where each row (if considered matrix) should have 3 strings of any length and a maximum of 10 rows

The data Structure is correct but I am very much surprised with the output I get in the global variable. So the matrix would act as the database to the program and hence kept in global space

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

// Maximum buffer size needed
#define MAX_NUM_ITEMS 10
#define MAX_ITEM_PER_ROW 3
static char *array[MAX_NUM_ITEMS][MAX_ITEM_PER_ROW];

#define ITOA_BASE_N (sizeof(unsigned)*CHAR_BIT + 1)
char *itoa_base(char *, int , int);
#define TO_BASE(x,b) itoa_base((char [ITOA_BASE_N]){0} , (x), (b))


char *itoa_base(char *s, int x, int base) {
  s += ITOA_BASE_N - 1;
  *s = '\0';
  if (base >= 2 && base <= 36) {
    int x0 = x;
    do {
      *(--s) = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"[abs(x % base)];
      x /= base;
    } while (x);
    if (x0 < 0) {
      *(--s) = '-';
    }
  }
  return s;
}

int main(void)
{   
    
    int count = 0;
    for (int i = 0; i < MAX_NUM_ITEMS; i++){
        for (int j = 0; j < MAX_ITEM_PER_ROW; j++ ){
            ++count;
            array[i][j] = TO_BASE(count, 16);
        }
    }
    
    for (int i = 0; i < MAX_NUM_ITEMS; i++){
        for (int j = 0; j < MAX_ITEM_PER_ROW; j++ ){
            printf("%s ",array[i][j]);
        }
        printf("\n");
    }
    return 0;
}

From my logic I should see 1 2 3 4 5 6 7 8 9 and so on and not E E E

can't understand why that is happening

like image 965
DevMac Avatar asked Mar 02 '23 10:03

DevMac


2 Answers

First, this:

(char [ITOA_BASE_N]){0}

Does not get you a new instance of a character array, unlike say golang. So, every time you call itoa(), you are calling it with the same character array. Somewhat worse, the character array is occupying a reclaimable stack address [ its scope is only that inner loop ], so it can be over written with random stuff shortly after. It is remarkably consistent though; I will give it that.

Changing the invocation to:

array[i][j] = strdup(TO_BASE(count, 16));

and adding a #include at the top produces the output you wanted.

If dynamic allocation is not permissible in your application, you will have to use a static allocation scheme, which you could make a bounded version of strdup like:

char *strnew(char *s) {
  static char strspace[ITOA_BASE_N * MAX_NUM_ITEMS * MAX_ITEM_PER_ROW ];
  static char *strnext = strspace;
  if (strlen(s) + strspace >= &strspace[sizeof strspace]) {
      s = "<error: out of space>"; /* something more clever is possible */
  } else {
      strcpy(strnext, s);
      s = strnext;
      strnext += strlen(strnext)+1;
  }
  return s;
}

which you could substitute for strdup. If you do the next person down the line a favour and use a more descriptive notion like MAX_STRING_SPACE which is based on the calculation; and rather than insert a "bad value", cause some sort of exception, I am sure they would appreciate it.

like image 83
mevets Avatar answered Mar 07 '23 10:03

mevets


The problem is here:

itoa_base((char [ITOA_BASE_N]){0} , (x), (b))
          ^^^^^^^^^^^^^^^^^^^^^^^

you are allocating a temp array (on the stack) which is only valid up to the end of the containing expression statement. So when the time comes to print them, the pointers you've stored in the matrix are dangling. What the compiler ends up doing is reusing the same memory for every call, so the strings end up overwriting.

You could instead use a static matrix of arrays rather than pointers:

static char array[MAX_NUM_ITEMS][MAX_ITEM_PER_ROW][ITOA_BASE_N];

then your call in the first loop becomes

itoa_base(array[i][j], count, 16);

you'll also need to "fix" itoa_base so it puts the result in the front of the array rather than the back. Obvious way is with a recursive loop like:

char *itoa_base(char *s, int x, int base) {
  if (base >= 2 && base <= 36) {
    if (x < 0) {
      *s++ = '-';
      x = -x; }
    if (x >= base)
      s = itoa_base(s, x/base, base);
    *s++ = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"[x % base];
  }
  *s = '\0';
  return s;
}
like image 42
Chris Dodd Avatar answered Mar 07 '23 10:03

Chris Dodd