1

I want to pass a structure to a function and store value in the structure's element. Here is my code.

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

typedef struct {
    uint32_t len;
    uint16_t *arr;
} seq;

void write_seq(seq *q, int val)
{
    // How to implement this function?

    uint16_t *tmp;
    tmp = (uint16_t *)malloc(sizeof(uint16_t));
    *tmp = val;

    *(q->arr + q->len) = *tmp;
    q->len += 1;

}

int main(int argc, char *argv[])
{
    seq q;
    q.len = 0;
    q.arr = NULL;
    int i;
    for (i = 0; i < 10; i++) {
        write_seq(&q, i);
    }

    printf("length is %d\n", q.len);
    for (i = 0; i < q.len; i++) {
        printf("%d\n", *(q.arr+i));

    }

    return 0;
}

I want to write 0 to 9 to a memory block which q.arr points to.
I don't want to use malloc in main(), since I don't know how many bytes I need before I call write_seq. I want to locate new memory every time when write_seq is called. Here's how output should look like.

length is 10
0
1
2
3
4
5
6
7
8
9

My implementation of write_seq() would cause core dumped. I don't know how to fix it. Thank you.

gsamaras
  • 71,951
  • 46
  • 188
  • 305
Lion Lai
  • 1,862
  • 2
  • 20
  • 41
  • 2
    Unrelated to your problem, but you do know that for any pointer or array `p` and index `i`, the expression `*(p + i)` is exactly equal to `p[i]`? You don't have to use pointer arithmetic, and can save typing a couple of characters. – Some programmer dude Oct 11 '17 at 07:56
  • Also, you have a memory leak, the pointer you allocate in `write_seq` is never deallocated. There's not even a *need* to allocate that single value dynamically. Just doing `q->arr[q->len++] = val;` is all the function needs to do. – Some programmer dude Oct 11 '17 at 07:57
  • Not yet understood what you're trying to achieve, but allocating memory for a single `uint16_t` is pointless. – Jabberwocky Oct 11 '17 at 07:57
  • Oh, and you have a much worse problem than all of the above, and one that makes me wonder why your program didn't crash. You dereference a null pointer, and write memory to unallocated memory. What I ***think*** you want to do in `write_seq` is to [reallocate](http://en.cppreference.com/w/c/memory/realloc) `q->arr`, not allocate the single temporary element. – Some programmer dude Oct 11 '17 at 07:59
  • 1
    On another unrelated note, for a size (like the structure member `len`) I suggest you use `size_t` instead. While it could be equal to `uint32_t` (on 32-bit platforms at least) it shows intent to the readers of your code. – Some programmer dude Oct 11 '17 at 08:07
  • @MichaelWalz I use `uint16_t` to remind me or whoever reads this block of code that `*arr` is an array to store unsigned 16 bit value. Does this make sense to you? – Lion Lai Oct 11 '17 at 08:19
  • @ZianLai it's entirely up to you. Yes, it makes sense, why not? – Jabberwocky Oct 11 '17 at 08:32
  • @Someprogrammerdude Can you explain more about why use `size_t` can make this code more self-explain to readers? I read this [post](https://stackoverflow.com/questions/1951519/when-should-i-use-stdsize-t), but not quite understand why using `size_t` is more preferable in this scenario. Thanks – Lion Lai Oct 11 '17 at 08:38
  • 1
    The `uint32_t` type is for a general 32-bit unsigned integer. It could store any kind of data, there's no meaning to the type beyond "32-bit unsigned integer". The type `size_t` really says "I'm a type for a size"., so readers will automatically know that the variable defined with that type is a size of some kind. – Some programmer dude Oct 11 '17 at 08:40

2 Answers2

1

You need to use realloc(), not malloc(), like this:

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

typedef struct {
    int len;
    int *arr;
} seq;

void write_seq(seq *q)
{
    q->arr = realloc (q->arr, (q->len + 1) *  sizeof(int));
    q->arr[q->len] = q->len;
    q->len++;
}

int main(void)
{
    seq q;
    q.len = 0;
    q.arr = NULL;

    for(int i = 0; i < 10; ++i)
        write_seq(&q);


    printf("length is %d\n", q.len);
    for (int i = 0; i < q.len; i++) {
        printf("%d\n", q.arr[i]);
    }

    free(q.arr);

    return 0;
}

Output:

length is 10
0
1
2
3
4
5
6
7
8
9
gsamaras
  • 71,951
  • 46
  • 188
  • 305
  • 3
    First of all, please avoid posting multiple answers. Modify the one you have instead. Secondly, code-only answers without comments or any kind of explanation are also discouraged. Why would this code be better than the code the OP already have? How does it solve the OPs problem? Lastly, don't reassign the result of `realloc` back to the pointer you pass as argument to `realloc`. The `realloc` call may fail and return NULL and then you will lose the original pointer and have a memory leak. – Some programmer dude Oct 11 '17 at 08:11
  • Doing a `realloc` for every single call to `write_seq` is inefficient. – Jabberwocky Oct 11 '17 at 08:34
  • also, you're only writing the length - the OP's function adds a given number to the `seq`. – Alnitak Oct 11 '17 at 08:56
1

To add members to the array when you don't know in advance the size of that array, you'd need to use realloc() to increase the size on demand. However it's inefficient to do that for every change to the array size, so it would be more usual to allow for a certain amount of headroom (whether that be a fixed increment or a percentage based amount) in the buffer.

The corollary of that is you need to store the current capacity of the buffer as well as the offset to the amount currently used.

This also means that there'll be a certain amount of memory wastage, but that's the trade off you have to make.

My approach would look something like this, abstracting the operations that you might want to perform on a seq.

typedef struct {
    size_t    capacity;
    size_t    offset;
    uint16_t *arr;
} seq;

static const size_t SEQ_INITIAL = 8;
static const size_t SEQ_INCREMENT = 8;

int seq_init(seq *seq) {
    assert(seq != NULL);         /* catch null seq */
    assert(seq->arr == NULL);    /* error to call on already init'd seq */
    seq->capacity = SEQ_INITIAL;
    seq->offset = 0;
    seq->arr = malloc(seq->capacity * sizeof(seq->arr[0]));
    return seq->arr == NULL ? -1 : 0;
}

static int seq_grow(seq *seq) {  /* private implementation detail */
    size_t new_capacity = seq->capacity + SEQ_INCREMENT;
    void *p = realloc(seq->arr, new_capacity * sizeof(seq->arr[0]));
    if (p == NULL) {             /* realloc failed, seq unmodified */
        return -1;
    }
    seq->arr = p;
    seq->capacity = new_capacity;
    return 0;
}

int seq_write(seq *seq, uint16_t value) {
    assert(seq != NULL);         /* catch null seq */ 
    if ((seq->offset == seq->capacity) && seq_grow(seq) < 0) {
        return -1;               /* write failed */
    }
    assert(seq->arr != NULL);    /* catch bad seq */
    assert(seq->offset < seq->capacity); /* ensure seq really has room */
    seq->arr[seq->offset++] = value;
    return 0;
}

void seq_free(seq *seq) {
    if (seq != NULL) {
        free(seq->arr);
        seq->arr = NULL;
    }
}
Alnitak
  • 334,560
  • 70
  • 407
  • 495
  • Your code works great. btw, I change one line to `size_t new_capacity = seq->capacity * 2;` I think maybe it's more efficient. – Lion Lai Oct 11 '17 at 09:28
  • 1
    @ZianLai that's the trade off you have to make between wasting memory or increasing performance. I chose the "incremental increase" approach because it limits the memory wastage to 8 elements. Doubling the capacity each time means you're using up to twice as much memory as you need to. A better compromise might be `seq->capacity * 1.4`. – Alnitak Oct 11 '17 at 12:15