1

For an experiment I created a function to initialize an array that have a built-in length like in java

int *create_arr(int len) {
    void *ptr = malloc(sizeof(int[len + 1]));
    int *arr = ptr + sizeof(int);
    arr[-1] = len;
    return arr;
}

that can be later be used like this

int *arr = create_arr(12);

and allow to find the length at arr[-1]. I was asking myself if this is a common practice or not, and if there is an error in what i did.

  • Your `malloc(sizeof(int[len + 1]))` is more idiomatic as `malloc((len+1) * sizeof(int))`. – Nate Eldredge Feb 25 '21 at 16:11
  • 6
    `ptr + sizeof(int)` (adding integer to `void*`) is illegal in the standard C. – MikeCAT Feb 25 '21 at 16:11
  • This is not legal. `malloc()` returns memory ["suitably aligned so that it may be assigned to a pointer to any type of object with a fundamental alignment requirement ..."](https://port70.net/~nsz/c/c11/n1570.html#7.22.3p1) This breaks that. It will work in this case simply because all the elements are `int` anyway, but in general it'll break things. – Andrew Henle Feb 25 '21 at 16:12
  • 1
    @AndrewHenle: I missed the `void *` arithmetic and so have deleted the comment. However, assuming the overall intended effect is to return `(int *)ptr + 1`, it should be fine as long as the returned pointer is only used to address `int`s. It wouldn't be a drop-in `malloc` replacement, but I'm not sure that it's supposed to be. – Nate Eldredge Feb 25 '21 at 16:13
  • @NateEldredge I noted you updated your comment and updated mine. – Andrew Henle Feb 25 '21 at 16:14
  • 1
    It should be `int *arr = ptr;` and `arr[0] = len;` – Jack Lilhammers Feb 25 '21 at 16:16
  • Pascal puts length in the first byte of strings. It was also commonly used in early Windows. Never saw it for ints. – stark Feb 25 '21 at 16:16

4 Answers4

1

First of all, your code has some bugs, mainly that in standard C you can't do arithmetic on void pointers (as commented by MikeCAT). Probably a more typical way to write it would be:

int *create_arr(int len) {
    int *ptr = malloc((len + 1) * sizeof(int));
    if (ptr == NULL) {
        // handle allocation failure
    }
    ptr[0] = len;
    return ptr + 1;
}

This is legal but no, it's not common. It's more idiomatic to keep track of the length in a separate variable, not as part of the array itself. An exception is functions that try to reproduce the effect of malloc, where the caller will later pass back the pointer to the array but not the size.

One other issue with this approach is that it limits your array length to the maximum value of an int. On, let's say, a 64-bit system with 32-bit ints, you could conceivably want an array whose length did not fit in an int. Normally you'd use size_t for array lengths instead, but that won't work if you need to fit the length in an element of the array itself. (And of course this limitation would be much more severe if you wanted an array of short or char or bool :-) )

Note that, as Andrew Henle comments, the pointer returned by your function could be used for an array of int, but would not be safe to use for other arbitrary types as you have destroyed the alignment promised by malloc. So if you're trying to make a general wrapper or replacement for malloc, this doesn't do it.

Nate Eldredge
  • 48,811
  • 6
  • 54
  • 82
  • Is there a portable and safe way of finding the minimum offset between a pointer returned by `malloc()` and the closest pointer that features the same alignment guarantees as pointers returned by `malloc()`? Alternatively, if there's no portable way, is there any definition in some header in major OSs (Windows/Mac/Linux/BSDs/etc) where this minimum distance between two "suitably aligned" pointers is specified? – cesss Apr 24 '22 at 14:58
  • 1
    @cesss: A pointer returned by malloc is guaranteed to have sufficient alignment for every type, so [`alignof(max_align_t)`](https://en.cppreference.com/w/c/types/max_align_t) would seem appropriate. – Nate Eldredge Apr 24 '22 at 21:11
  • Thanks a lot. While thinking about this, it came to my mind that perhaps flexible array members were introduced for exactly the same purpose the OP was asking. So, I tried to experiment about that, and it works, but I'm not sure if I'm violating any rule in the C language, so I asked this question about the experiment: https://stackoverflow.com/questions/71993225/is-it-legal-c-to-obtain-the-pointer-to-a-struct-from-the-pointer-to-its-2nd-memb – cesss Apr 24 '22 at 23:52
1

Apart from the small mistakes that have already been pointed in comments, this is not common, because C programmers are used to handle arrays as an initial pointer and a size. I have mainly seen that in mixed programming environments, for example in Windows COM/DCOM where C++ programs can exchange data with VB programs.

Your array with builtin size is close to winAPI BSTR: an array of 16 bits wide chars where the allocated size is at index -1 (and is also a 16 bit integer). So there is nothing really bad with it.

But in the general case, you could have an alignment problem. malloc does return a pointer with a suitable alignment for any type. And you should make sure that the 0th index of your returned array also has a suitable alignment. If int has not the larger alignment, it could fail...

Furthermore, as the pointer is not a the beginning of the allocated memory, the array would require a special function for its deallocation. It should probaby be documented in a red flashing font, because this would be very uncommon for most C programmers.

Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
1

This technique is not as uncommon as people expect. For example stb header only library for image processing uses this method to implement type safe vector like container in C. See https://github.com/nothings/stb/blob/master/stretchy_buffer.h

tstanisl
  • 13,520
  • 2
  • 25
  • 40
0

It would be more idiomatic to do something like:

struct array {
        int *d;
        size_t s;
};

struct array *
create_arr(size_t len)
{
        struct array *a = malloc(sizeof *a);
        if( a ){
                a->d = malloc(len * sizeof *a->d);
                a->s = a->d ? len : 0;
        }
        return a;
}
William Pursell
  • 204,365
  • 48
  • 270
  • 300