1

UPDATE: I solved my problem (scroll down).


I'm writing a small C program and I want to do the following:

The program is connected to a mysql database (that works perfectly) and I want to do something with the data from the database. I get about 20-25 rows per query and I created my own struct, which should contain the information from each row of the query.

So my struct looks like this:

typedef struct {
    int timestamp;
    double rate;
    char* market;
    char* currency;
} Rate;

I want to pass an empty array to a function, the function should calculate the size for the array based on the returned number of rows of the query. E.g. there are 20 rows which are returned from a single SQL query, so the array should contain 20 objectes of my Rate struct.

I want something like this:

int main(int argc, char **argv)
{
    Rate *rates = ?; // don't know how to initialize it
    (void) do_something_with_rates(&rates);

    // the size here should be ~20
    printf("size of rates: %d", sizeof(rates)/sizeof(Rate));
}

How does the function do_something_with_rates(Rate **rates) have to look like?


EDIT: I did it as Alex said, I made my function return the size of the array as size_t and passed my array to the function as Rate **rates.

In the function you can access and change the values like (*rates)[i].timestamp = 123 for example.

beeef
  • 2,664
  • 4
  • 17
  • 27
  • `sizeof(rates)/sizeof(Rate)` is [a wrong way to determine number of elements of what is pointed by a pointer variable](http://stackoverflow.com/questions/492384/how-to-find-the-sizeofa-pointer-pointing-to-an-array). The format specifier is also wrong and it will invoke *undefined behavior*. – MikeCAT Jul 24 '16 at 00:11
  • OK thank you. But I think that's not the problem here.. – beeef Jul 24 '16 at 00:12
  • @beeef If `rates`'s size isn't constant, you'll have to do `Rate *rates = malloc(sizeof(Rate) * size_of_new_array);`. In that case, `sizeof` won't work for computing that array's size because it can only compute the size for statically allocated arrays. – Alex Jul 24 '16 at 00:17
  • So if I do `Rate *rates = malloc(num_of_rows * sizeof(Rate))` it's not working? – beeef Jul 24 '16 at 00:18
  • And how do I set the values of the struct? How do I access the members? Like `rates[0].timestamp = 12345567`? – beeef Jul 24 '16 at 00:19
  • Sorry, made a mistake, edited it. No it still won't work 'cause you still won't know how big the old array was, nor would you know how big the new array is after you returned from `do_something_with_rates()` – Alex Jul 24 '16 at 00:20
  • Yes that's how that'll work – Alex Jul 24 '16 at 00:20
  • I suggest you use the return value of `do_something_with_rates` for something useful, rather than just `void`. Like, I dunno, telling the *caller* how big the array that was just internally crafted *is*. – WhozCraig Jul 24 '16 at 00:21
  • So what should I do? Should I create an array with a fixed size like `Rate rates[50];` and pass that to my function? – beeef Jul 24 '16 at 00:22
  • Maybe someone could answer my question with a good example? – beeef Jul 24 '16 at 00:23
  • @Alex "... it can only compute the size for statically allocated arrays." `char (*I_disagree)[42] = NULL; sizeof *I_disagree`... – autistic Jul 24 '16 at 09:22

2 Answers2

4

In C, memory is either dynamically or statically allocated.

Something like int fifty_numbers[50] is statically allocated. The size is 50 integers no matter what, so the compiler knows how big the array is in bytes. sizeof(fifty_numbers) will give you 200 bytes here.

Dynamic allocation: int *bunch_of_numbers = malloc(sizeof(int) * varying_size). As you can see, varying_size is not constant, so the compiler can't figure out how big the array is without executing the program. sizeof(bunch_of_numbers) gives you 4 bytes on a 32 bit system, or 8 bytes on a 64 bit system. The only one that know how big the array is would be the programmer. In your case, it's whoever wrote do_something_with_rates(), but you're discarding that information by either not returning it, or taking a size parameter.

It's not clear how do_something_with_rates() was declared exactly, but something like: void do_something_with_rates(Rate **rates) won't work as the function has no idea how big rates is. I recommend something like: void do_something_with_rates(size_t array_size, Rate **rates). At any rate, going by your requirements, it's still a ways away from working. Possible solutions are below:

You need to either return the new array's size:

size_t do_something_with_rates(size_t old_array_size, Rate **rates) {
    Rate **new_rates;
    *new_rates = malloc(sizeof(Rate) * n); // allocate n Rate objects

    // carry out your operation on new_rates

    // modifying rates
    free(*rates); // releasing the memory taken up by the old array
    *rates = *new_rates // make it point to the new array

    return n; // returning the new size so that the caller knows
}

int main() {
    Rate *rates = malloc(sizeof(Rate) * 20);
    size_t new_size = do_something_with_rates(20, &rates);
    // now new_size holds the size of the new array, which may or may not be 20

    return 0;
}

Or pass in a size parameter for the function to set:

void do_something_with_rates(size_t old_array_size, size_t *new_array_size, Rate **rates) {
    Rate **new_rates;
    *new_rates = malloc(sizeof(Rate) * n); // allocate n Rate objects
    *new_array_size = n; // setting the new size so that the caller knows

    // carry out your operation on new_rates

    // modifying rates
    free(*rates); // releasing the memory taken up by the old array
    *rates = *new_rates // make it point to the new array
}

int main() {
    Rate *rates = malloc(sizeof(Rate) * 20);
    size_t new_size;
    do_something_with_rates(20, &new_size, &rates);
    // now new_size holds the size of the new array, which may or may not be 20

    return 0;
}

Why do I need to pass the old size as a parameter?

void do_something_with_rates(Rate **rates) {
    // You don't know what n is. How would you
    // know how many rate objects the caller wants
    // you to process for any given call to this?
    for (size_t i = 0; i < n; ++i)
        // carry out your operation on new_rates
}

Everything changes when you have a size parameter:

void do_something_with_rates(size_t size, Rate **rates) {
    for (size_t i = 0; i < size; ++i) // Now you know when to stop
        // carry out your operation on new_rates
}

This is a very fundamental flaw with your program.

I want to also want the function to change the contents of the array:

size_t do_something_with_rates(size_t old_array_size, Rate **rates) {
    Rate **new_rates;
    *new_rates = malloc(sizeof(Rate) * n); // allocate n Rate objects

    // carry out some operation on new_rates
    Rate *array = *new_rates;
    for (size_t i = 0; i < n; ++i) {
        array[i]->timestamp = time();
        // you can see the pattern
    }

    return n; // returning the new size so that the caller knows
}
Alex
  • 3,111
  • 6
  • 27
  • 43
  • Why do I need to pass the old size as parameter if I don't need it? – beeef Jul 24 '16 at 07:20
  • I also want the `do_something_with_rates` function to change the contents of the array, not only calculate the new size ... – beeef Jul 24 '16 at 07:24
  • There are more than two forms of storage duration: static, automatic, allocated and register to name C99s and there's an additional thread-specific storage duration in C11. Also, if you use a 16-bit compiler you'll be much less likely to have a 32-bit `int` representation, and you can even run that 16-bit program in an emulated environment... so it might be a good idea to lose the correspondence between integer representation and underlying hardware designs... There are systems where `sizeof (int)` is 4 (or even 2, 1 is also possible) despite the hardware only exposing 64-bit types. – autistic Jul 24 '16 at 09:09
  • If `rates` is `NULL` or engineered to always contain a `NULL` (using it as a terminal value, like the `'\0'` at the end of a string), then we don't need to know how large the array is. To insert, find the first `NULL`, and if the position found is a binary power (e.g. 1, 2, 4, 8, 16, ...) double the size of the array using `realloc`. Just saying so you realise, the caller doesn't need to store the size externally... – autistic Jul 24 '16 at 09:18
  • @beeef edited my answer. As Seb suggested, there are many ways to do this, each with their advantages and disadvantages. We have no idea what your requirements are, nor do we know what your current implementation of `do_something()` looks like, so we can't guess – Alex Jul 24 '16 at 13:59
0

sizeof produces a value (or code to produce a value) of the size of a type or the type of an expression at compile time. The size of an expression can therefore not change during the execution of the program. If you want that feature, use a variable, terminal value or a different programming language. Your choice. Whatever. C's better than Java.

char foo[42];

foo has either static storage duration (which is only partially related to the static keyword) or automatic storage duration.

Objects with static storage duration exist from the start of the program to the termination. Those global variables are technically called variables declared at file scope that have static storage duration and internal linkage.

Objects with automatic storage duration exist from the beginning of their initialisation to the return of the function. These are usually on the stack, though they could just as easily be on the graph. They're variables declared at block scope that have automatic storage duration and internal linkage.

In either case, todays compilers will encode 42 into the machine code. I suppose it'd be possible to modify the machine code, though that several thousands of lines you put into that task would be much better invested into storing the size externally (see other answer/s), and this isn't really a C question. If you really want to look into this, the only examples I can think of that change their own machine code are viruses... How are you going to avoid that antivirus heuristic?

Another option is to encode size information into a struct, use a flexible array member and then you can carry both the array and the size around as one allocation. Sorry, this is as close as you'll get to what you want. e.g.

struct T_vector {
    size_t size;
    T value[];
};

struct T_vector *T_make(struct T_vector **v) {
    size_t index = *v ? (*v)->size++ : 0, size = index + 1;
    if ((index & size) == 0) {
        void *temp = realloc(*v, size * sizeof *(*v)->value);
        if (!temp) {
            return NULL;
        }
        *v = temp;
        // (*v)->size = size;
        *v = 42; // keep reading for a free cookie
    }
    return (*v)->value + index;
}

#define T_size(v) ((v) == NULL ? 0 : (v)->size)

int main(void) {
    struct T_vector *v = NULL; T_size(v) == 0;
    { T *x = T_make(&v); x->value[0]; T_size(v) == 1;
      x->y = y->x; }
    { T *y = T_make(&v); x->value[1]; T_size(v) == 2;
      y->x = x->y; }
    free(v);
}

Disclaimer: I only wrote this as an example; I don't intend to test or maintain it unless the intent of the example suffers drastically. If you want something I've thoroughly tested, use my push_back.

This may seem innocent, yet even with that disclaimer and this upcoming warning I'll likely see a comment along the lines of: Each successive call to make_T may render previously returned pointers invalid... True, and I can't think of much more I could do about that. I would advise calling make_T, modifying the value pointed at by the return value and discarding that pointer, as I've done above (rather explicitly).

Some compilers might even allow you to #define sizeof(x) T_size(x)... I'm joking; don't do this. Do it, mate; it's awesome!

Technically we aren't changing the size of an array here; we're allocating ahead of time and where necessary, reallocating and copying to a larger array. It might seem appealing to abstract allocation away this way in C at times... enjoy :)

autistic
  • 1
  • 3
  • 35
  • 80