5

I want to share the value of sizeof(array) between two .c modules. The array is initialized in file A, so compiler knows the size at compile time and then I want to use the size of this array in another B file.

Example:
In A.c file:

int arr[] = {1, 2, 3};
.
.
.

for (int i = 0; i < sizeof(arr); i++); // that works

In A.h file:

extern int arr[];

In B.c file:

#include "A.h"
.
.
.

for (int i = 0; i < sizeof(arr); i++); // here compiler doesn't know size of this arr

Is there a way to make this work? I know why this doesn't work but maybe there is a sneaky trick to workaround this.

dunajski
  • 381
  • 3
  • 15

5 Answers5

2

Is there a way to make this work? I know why this doesn't work but maybe there is a sneaky trick to workaround this.

This unfortunately is not very sneaky, but it works...

In some.h

//declare as extern for project global scope
extern int arr[];
extern size_t gSizeArray;

Then in some.c

//define and initialize extern variables in 1 (and only one) .c file, 
int arr[] = { 1,2,3 };
size_t gSizeArray = sizeof(arr)/sizeof(arr[0]);

In someother.c that includes some.h

#include "some.h"
//you can now see the value of gSizeArray in this file

printf("%zu\n%d,%d,%d\n", gSizeArray, arr[0], arr[1], arr[2]);//should output

3
1,2,3

caveat
The value of using an extern in this fashion is that the value of that variable can change in one module, and that same value can be seen in any .c file that includes some.h

corollary
The problem of using an extern in this fashion is that the value of that variable can change in one module, and that same value can be seen in any .c file that includes some.h.

(or in other words, be careful. Globals, in particular extern globals can be helpful, but they can also be dangerous. Particularly for maintainers of the code who are not also the author of the code, who may not be aware a variable is of extern scope, and use it unwittingly in inappropriate ways.

By the way, there is probably more than you'll ever want to know about using extern scope in this post.

ryyker
  • 22,849
  • 3
  • 43
  • 87
  • 2
    It's safe if you make the `gSizeArray` `const`, but it's still a bit wasteful and not as flexible as an integer constant expression. – Petr Skocik Jun 26 '20 at 12:49
  • @PSkocik - I try to be explicit when naming anything that has global scope to encourage future maintainers to see where and how it is defined. But I do favor flexibility over safety in this instance. – ryyker Jun 26 '20 at 12:53
  • 1
    @PSkocik - Thank you! I had just opened the editor, and you beat me to it. – ryyker Jun 26 '20 at 12:55
  • Correct me if I am wrong, some.c have to be compiled before someother.c? – dunajski Jun 26 '20 at 20:14
  • 1
    That const is game changer. – dunajski Jun 26 '20 at 20:16
  • @dunajski - Yes, const does change things, and I prefer not using it in this case :). And, the compiler will and linker will ensure the dependencies are all treated properly and in the right order. – ryyker Jun 26 '20 at 20:24
  • @ryyker do you mean that, there is no need to compile some.c first? I tought that if I want to use gSizeArray in another modules with proper value that I have to compile it first to know array size. – dunajski Sep 29 '21 at 11:28
  • @dunajski - Yes, the compiler will do all the work, and the order source files are compiled does not matter. The C standard does not specify order of compilation. And although some compilers make provision to do so, there is no advantage, or any reason AFAIK to order them. The resulting binary is the product of _all_ source files submitted to the build stream equally, regardless of order. BTW, ,Regarding the `const` keyword, its attributes are applied during the construction of the binary at compile time, unlike macros, or pre-compile defines. – ryyker Sep 29 '21 at 13:01
  • @dunajski - (read more in this post about [compilation order](https://stackoverflow.com/questions/705866/how-is-the-order-of-compilation-of-source-files-decided).) So, because `gSizeArray` is declared in a location that will be visible to any file that includes it with a `#include` ( `extern size_t gSizeArray` ) , then it is defined once in only one `.c` file ( `size_t gSizeArray = sizeof(arr)/sizeof(arr[0]);` ) array size will be known immediately at run-time to any other translation unit that included the header file that declared it. – ryyker Sep 29 '21 at 13:10
1

Make the initializer a macro, share it through the header, and then you can use the compiler to recount the elements for the extern declaration:

//header.h
#define ARR_INIT { 1, 2, 3}
extern int arr[sizeof((int[])ARR_INIT)/sizeof(int)];

//file.c
#include "header.h"
int arr[] = ARR_INIT;

( On gcc/clang/tinycc, you can also do:

//header.h
#define ARR_INIT (int[]){ 1, 2, 3}
extern int arr[sizeof(ARR_INIT)/sizeof(ARR_INIT[0])];

//file.c
#include "header.h"
int arr[] = ARR_INIT;

which is more typesafe but won't work with -Wpedantic as standard C doesn't allow compound literals as initializers for objects with static storage duration. )

You could also share the size through a regular global, but that has disadvantages as such a size would no longer be an integer constant expression and so it wouldn't be usable where those are required (array sizes, bitfield-widths, case labels, static initializers).

Petr Skocik
  • 58,047
  • 6
  • 95
  • 142
1

You can do it f.e. by declaring both, the size object (which contains the amount of elements of arr) and the array arr, with the extern storage-class specifier so that they have external linkage in the header A.h. This header you include in both .c source files.

The value of the size object as well as the size of the array arr (by its initializer list) is specified in A.c but can be used in B.c.


The A.h file:

 #include <stdio.h>     // for size_t.

 extern int arr[];
 extern size_t size;

The A.c file:

 #include "A.h"
 #include <stdio.h>     // for size_t. 

 int arr[] = { 1, 2, 3 };
 size_t size = sizeof (arr) / sizeof (arr[0]);

 for ( size_t i = 0; i < size; i++ ) 
 {
      arr[i] = arr[i] - 1;
 }

The B.c file:

 #include "A.h"
 #include <stdio.h>

 for ( size_t i = 0; i < size; i++ ) 
 {
      printf("%d\n", arr[i]);
 }

Output:

 0
 1 
 2

Side Note:

  • sizeof(arr) does not yield the amount of elements in arr; It yields the amount of bytes allocated for the whole array arr. Since arr is an array of type int and not f.e. char, where the amounts of elements is equal to the amount of allocated byte, this result is different to the amount of elements.

    To gain the amount of elements use sizeof(arr) / sizeof(arr[0]).

0

no magic needed or another global variables.

in the .h file

extern int arr[3];

and then

int main(void)
{
    printf("%zu\n", sizeof(arr)/ sizeof(arr[0]));
}

0___________
  • 60,014
  • 4
  • 34
  • 74
  • 1
    The `[]` (as opposed to `[3]`) in the question kind of suggests the OP doesn't want to keep track of the count manually. If that's true, a little bit of magic *is* required to share the automatically computed count across translation units. – Petr Skocik Jun 26 '20 at 13:04
  • but it a bad practice. More generally it is how to design the static global pool memory - but the way of global [] array and another global variables is not the answer. – 0___________ Jun 26 '20 at 13:09
0

When you say extern int arr[]; in the header file, you tell the compiler that:

  • This variable is defined later. It isn't allocated here, but elsewhere.
  • The size of this array is unknown (incomplete). I'll complete it later when I define the variable.

C works with something called translation units. A translation unit is a .c file and all the headers it includes. In your example A.c + A.h forms one translation unit, and B.c + A.h forms another. These are compiled independently.

In translation unit A.c + A.h, the compiler defines the variable and also gives it a size (3 int). Therefore the size of this array is known throughout that translation unit.

In translation unit B.c + A.h, you never define the variable. The compiler just knows that the array is defined "elsewhere", but it has no way to know the size.


The cause is extern spaghetti programming, where the same global variable is shared all over the place. The proper solution is to never do that. Use sound program design with private encapsulation instead. Let the array be allocated where it makes sense for it to be. Something along the lines of this (silly example):

array.h

int    get_array_item (size_t i);
void   set_array_item (size_t i, int val);
size_t get_array_size (void);

array.c

#include "array.h"

static int arr[] = {1,2,3};

int get_array_item (size_t i)
{
  assert(i < sizeof arr); // optional error handling
  return array[i];
}

void set_array_item (size_t i, int val)
{
  assert(i < sizeof arr); // optional error handling
  array[i] = val;
}

size_t get_array_size (void)
{
  return sizeof arr;
}

In a real application, this "array" would of course be something meaningful instead.

Lundin
  • 195,001
  • 40
  • 254
  • 396