How can I use the size of an external array at compile time in C?

Viewed 177

I have the following snippet from some foo.c file:

unsigned char foo[] = {1, 2, 3, 4};

void copy_foo(unsigned char *dest)
{
    memcpy(dest, foo, sizeof(foo));
}

And I want to declare a bar array in another C file (main.c) that has the same size as foo, to hold the output of copy_foo:

int main(void)
{
    unsigned char bar[?];
    copy_foo(bar);
    return 0;
}

Is it possible to get access to sizeof(foo) from main.c at compile time, so that it could be used to declare the bar array?

The usual "solution" to this problem is to use some sort of heap allocation to allocate memory at runtime, but I am looking for a static, compile time solution, as some embedded systems prohibit the use of dynamic allocation.

Of course, I can add #define FOO_SIZE 4 to foo.h, but then if foo changes FOO_SIZE will have to be manually changed as well.

Is this possible?

4 Answers

Here's the solution I found, for anyone interested. If anyone has a better solution I would still be interested to see it :)

main.c:

#include "foo.h"

int main(void)
{
    unsigned char bar[FOO_SIZE];
    copy_foo(bar);
    return FOO_SIZE;
}

foo.h:

#define FOO (unsigned char []){1, 2, 3, 4}
#define FOO_SIZE sizeof(FOO)

void copy_foo(unsigned char *dest);

foo.c:

#include <string.h>
#include "foo.h"

unsigned char foo[] = FOO;

void copy_foo(unsigned char *dest)
{
    memcpy(dest, foo, FOO_SIZE);
}

This works thanks to "Compound literals", which allow us to define "anonymous" arrays. FOO is defined as one of those compound literals, and it can be used to initialize the foo array. The interesting part is that it could also be used inside a sizeof, so the line:

unsigned char bar[FOO_SIZE];

evaluates to:

unsigned char bar[sizeof((unsigned char []){1, 2, 3, 4})];

which is perfectly legal.

Now to prove that this really evaluates to 4 at compile time, and that it doesn't add extra copies of the array to the data section:

I compiled with:

gcc main.c foo.c -S

In foo.s:

    .globl  foo
    .data
    .type   foo, @object
    .size   foo, 4
foo:
    .ascii  "\001\002\003\004"

There is no such array in main.s so the compiler didn't copy the array. This makes sense, because the array is declared inside a sizeof, so the compiler should be smart enough to optimize it and replace it with a 4.

To check that the value is available at compile time, I returned it as the return value of main, which compiled to:

    movl    $4, %eax
    movq    -8(%rbp), %rdx
    xorq    %fs:40, %rdx
    je  .L3
    call    __stack_chk_fail@PLT
.L3:
    leave
    .cfi_def_cfa 7, 8
    ret

The return value is passed through the eax register, and we can see that it got the immediate value of 4, which proves that the compiler knew what that value is.

Add this to the header file:

// foo.h
extern const size_t foo_len;

And add this right below the definition of the array:

// foo.c
const size_t foo_len = sizeof foo / sizeof foo[0];

And then you can use foo_len in your main function or wherever you want, without having to worry about manually maintaining that value.

This, however, is the only way to make this work in C, as the lengths of static arrays are lost after compiling, which happens before linking. This means you have to add similar lines for every array you want to use their predefined lengths.

You can use just a static array with accessor.

// foo.h
static unsigned char _foo[] = {1,2,3,4};
#define FOO_SIZE  (sizeof(_foo))
unsigned char *foo(void); // if you ever need to use _foo
void copy_foo(unsigned char*);

// foo.c
unsigned char *foo(void) {
      return _foo;
}
void copy_foo(unsigned char *dest) {
    memcpy(dest, foo(), FOO_SIZE);
}

// main.c
#include "foo.h"    
int main(void) {
    unsigned char bar[FOO_SIZE];
    copy_foo(bar);
    return FOO_SIZE;
}

Just make sure you never ever use _foo anywhere in your code except in foo.c, because it's static and exists in each source file that includes foo.h. Compiler will remove unused static global variables from all source files leaving only a single _foo in foo.c file.

If the array is defined in a different module, it must be declared in a common header file with a specified length for every module to use or check consistency:

foo.h

extern unsigned char foo[4];

foo.c

#include "foo.h"

// if the size in foo.h is not correct, this will cause an error
unsigned char foo[] = { 1, 2, 3, 4 };

void copy_foo(unsigned char *dest) {
    memcpy(dest, foo, sizeof(foo));
}

main.c

#include "foo.h"

int main() {
    unsigned char bar[sizeof(foo) / sizeof(*foo))];
    copy_foo(bar);
    return 0;
}

If the length of the array cannot be specified in the header file, you could define an external variable foo_len, but it will not be a constant expression, so bar will be a VLA (variable length array, a C99 feature made optional in C17).

foo.h

extern unsigned char foo[]; // optional
extern int foo_len;

foo.c

#include "foo.h"

// if the size in foo.h is not correct, this will cause an error
unsigned char foo[] = { 1, 2, 3, 4 };
int foo_len = sizeof(foo) / sizeof(*foo);

void copy_foo(unsigned char *dest) {
    memcpy(dest, foo, sizeof(foo));
}

main.c

#include "foo.h"

int main() {
    unsigned char bar[foo_len];
    copy_foo(bar);
    return 0;
}
Related