4
\$\begingroup\$

I have a block of text where several arrays are printed out, with a name and values:

VAL1=10 20 30 40 50

VAL2=4 8 15 16 23 42

I have a function that looks for the value name, and sets an array of these values:

bool FetchValueArray(char* source, char* name, char* typeFormat, int count, void* destination, size_t destElementSize)
{
    int i;
    char *t;
    t=strstr(source,name);
    if (t)
        if (destination != NULL)
        {
            for (i = 0;i < count;i++)
                sscanf(t, typeFormat, (char*)destination + destElementSize * i);
            return true;
        }
    return false;
}

Called thus:

FetchValueArray(source, "VAL1=", "%d", 5, val1array, sizeof(val1array[0]);
FetchValueArray(source, "VAL2=", "%d", 6, val1array, sizeof(val1array[0]);

This is from a StackOverflow question. As the answerer wrote, it works, but it's not safe. If I call it with a count that is higher than the size of the array, I will corrupt the memory. I'm trying to wrap my head around this one and having a tough time. How can I have a function that will set values to a fixed array and be "safe"?

\$\endgroup\$

2 Answers 2

4
\$\begingroup\$

I'm not quite sure what you are asking for, but I guess you are looking for a solution similar to this (not compiled nor tested):

#include <stdio.h>   /* sscanf */
#include <stddef.h>  /* NULL */
#include <stdbool.h> /* C99 bool type */

typedef enum
{
  TYPE_CHAR  = 'c',
  TYPE_INT   = 'd',
  TYPE_FLOAT = 'f',
  // etc
} Type_t;



/* I placed destination first to use the same parameter order as standard library
   functions (strstr for example). Note the const correctness for read-only parameters. */

bool FetchValueArray (void*       dest, 
                      const char* source,
                      const char* name,
                      size_t      count,
                      Type_t      type)

{
  const char* strptr;                  /* Some string pointer (not sure what it does) */
  BOOL is_found;                       /* Is the name found? */

  if(dest == NULL)                     /* No need to do anything if this parameter is NULL */
  {
    return false;
  }


  strptr = strstr(source, name);
  is_found = strptr != NULL;           /* Boolean arithmetic */

  if(is_found)
  {
    size_t i;
    size_t item_size;
    const char format[2] = {'%', (char)type};

    switch(type)
    {
      case TYPE_CHAR:  item_size = 1; break;
      case TYPE_INT:   item_size = 4; break;
      case TYPE_FLOAT: item_size = 4; break;
    }

    for(i=0; i<count; i++)
    {
      sscanf(strptr, format, (char*)dest + item_size*i);
    }
  }

  return is_found;
}

EDIT: Regarding array bounds checking. First you have to decide whether this is actually your concern or if you should leave it up the caller. Most commonly, you document every parameter in the .h file and state how the function should be called. If someone still decides to call it with incorrect parameters, it is then their own fault. That's how most C functions work and in this particular case, that's what I would recommend.

Since C does not store the array size together with the array as one single data type, you can't really pass an array size "safely" between the caller and a function, the caller is the one with the knowledge about the size, so you simply have to trust the programmer writing the caller.

You could write something like this

bool FetchValueArray(char dest[25], ...

but there are still no guarantees by the standard that this is safe. Some compilers will toss a warning if you try to pass anything but a fixed char array of 25 items to this function, but they don't have to. And we'd lose the generic type, so it isn't helpful.

You could do something like this, but I would not recommend it:

#define SIZE 25

typedef union
{
  char  char_array[SIZE];
  int   int_array[SIZE];
  float float_array[SIZE];

} GenericSafeArray_t;

This will allocate SIZE*sizeof(float), since float happend to be the largest type. So it is memory-ineffective and not particularly flexible either.

\$\endgroup\$
2
  • \$\begingroup\$ I like the enum. I'm still not convinced on safety though. What's to prevent me (or someone else) from calling it with a int[2] as the target and a count of 10? \$\endgroup\$
    – MPelletier
    Oct 5, 2011 at 14:01
  • \$\begingroup\$ @MPelletier Edited my post with an answer. \$\endgroup\$
    – Lundin
    Oct 6, 2011 at 6:49
2
\$\begingroup\$

Now there are some tricks if you have a real array:

int   data[5];
sizeof(data)                  => sizeof(int) * 5
sizeof(data)/sizeof(data[0])  => Number of elements in the array
                                 Note this is also safe if the array has zero elements
                                 As sizeof is evaluated at compile time not run-time.

so if you have a real array then you can call your method like this:

int    val1array[5];
FetchValueArray(source, "VAL1=", "%d",
                sizeof(val1array)/sizeof(val1array[0]), 
                val1array,
                sizeof(val1array[0])
               );

The trouble is this seems to work and compiles if val1array is a pointer to an array. Unfortunately in this case sizeof(val1array) will return the size of the pointer, and sice you can not pass arrays as arguments to functions in C (they decay into pointers) there is no way to determine the size of the array on the other side of the function.

So (Rule of Thumb) if you ever pass an array as a parameter then you should also pass its size using the trick above and pass that information forward with the array at all times.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.