1

I have a problem where i am linking multiple files in C. I want to define a constant representing array length at compile time, but save the user having to implement this in their file every time.

Here's the gist:

data.h - defines some constants

extern const int data[];
extern const int DATA_SIZE;
//other functions, not relevant
int get_second_item(void);

library_data.c - implements some of the functions in data.h

#include "data.h"
const int DATA_SIZE = sizeof(data) / sizeof(data[0]); 
//can't compile because data is an incomplete type and hasn't been defined yet

int get_second_item(void) 
{
    return data[1];
}

public_data.c - user changes this with their data.

#include "data.h"
const int data[] = {1, 2, 3};

library_data.c and data.h are compiled first as .o files, amongst other library files that #include data.h and need to therefore use DATA_SIZE. Moving

const int DATA_SIZE = sizeof(data) / sizeof(data[0]) 

to public_data.c will of course work, but isn't a neat solution.

aong152
  • 143
  • 2
  • 6
  • Why aren't `data[]` and `DATA_SIZE` defined in the *same file* (namely public_data.c, seemingly an appropriate place as the name suggests)? – WhozCraig Jan 24 '18 at 01:04
  • It would require every person who defines public_data to define DATA_SIZE which is *cumbersome*, considering it's literally the same line no matter what their data is. I know that it is a solution and will work, but wondering if there is a better way around it where the user just has to define their data and the library takes care of the rest. – aong152 Jan 24 '18 at 01:07
  • 2
    The "better" way would be avoiding globals in the first place and passing the data, and it's magnitude, as arguments to the various APIs being exposed. As a hideous and vile work-around, you could require users defined their `data` via a macro you provide that, hidden to them, also lays down the magnitude constant. I strongly advise against such cruel punishment to future maintainers of the code, however. – WhozCraig Jan 24 '18 at 01:12
  • 1
    Why don't you just make DATA_SIZE a function? Or what about making a class to hold the abstraction you need. – Uzer Jan 24 '18 at 01:15
  • @JonathanKelsey It's not clear what you mean here by "class". – aschepler Jan 24 '18 at 01:26
  • This problem is actually a sub-problem of a bigger problem. Users define data (up to 32 elements), and we have functionality that lets you select/unselect items. It is implemented via a bitmask (this is in an embedded situation). In an ideal world, we ultimately want to `typedef uint8/uint16/uint32 data_chosen_mask`. However on doing more research, it isn't possible to do a typedef without statically knowing the array size, which can't even be calculated using `sizeof(data)/sizeof(data[0]).` In the end the solution was to just force users to `#define data_size.` – aong152 Jan 24 '18 at 06:36
  • I mean it seems you want features best served in OOP. I know C does not have native classes (depends on your compiler really). But you can simulate one. https://stackoverflow.com/questions/1403890/how-do-you-implement-a-class-in-c – Uzer Jan 24 '18 at 14:45
  • So build a 'class' which has a method which takes the array. Calculates the size, and then stores the array and the length of the array in the instantiated class. The class exposes an API to get a pointer to the data and the length of the array. Defo try to get rid of the globals if possible. – Uzer Jan 24 '18 at 14:47
  • That works for determining the data_size at run-time, but doesn't allow us to do typedef's based on data_size. Hence why the question says "at compile time". If there weren't types that relied on the size of the array, then calculating/caching at run-time (whether with class or a global) is very easily implemented. – aong152 Jan 26 '18 at 04:23

2 Answers2

2

You can't use sizeof on an extern array of unspecified size (e.g. extern const int data[];)

From http://c-faq.com/decl/extarraysize.html:

An extern array of unspecified size is an incomplete type; you cannot apply sizeof to it. sizeof operates at compile time, and there is no way for it to learn the size of an array which is defined in another file.

You have three options:

  1. Declare a companion variable, containing the size of the array, defined and initialized (with sizeof) in the same source file where the array is defined:

    file1.c: file2.c:

    int array[] = {1, 2, 3}; extern int array[];
    int arraysz = sizeof(array); extern int arraysz;

    (See also question 6.23.)

  2. #define a manifest constant for the size so that it can be used consistently in the definition and the extern declaration:

    file1.h:

    #define ARRAYSZ 3
    extern int array[ARRAYSZ];

    file1.c: file2.c:

    #include "file1.h" #include "file1.h"
    int array[ARRAYSZ];

  3. Use some sentinel value (typically 0, -1, or NULL) in the array's last element, so that code can determine the end without an explicit size indication:

    file1.c: file2.c:

    int array[] = {1, 2, 3, -1}; extern int array[];

Mike Holt
  • 4,452
  • 1
  • 17
  • 24
  • Thanks - the 3 solutions were what I came up with, but unfortunately all require the user on the other end to do something. Maybe extern int arraysz is the best solution. – aong152 Jan 24 '18 at 02:46
1

One solution might be to add an initialization function to data.c, and then you'll have to make sure that when any programs are built the function is called to prepare the data size.

int initupdate_datasize() {

  DATA_SIZE = sizeof(data) / sizeof(data[0]);

  return 0;

}

You'll have to remove the const from the data.h, and library_data.c

Another way would be to pass a compiler define such as -DDATASZ=96 that you would get to all source files that need it. For any given platform you could update it manually if it rarely changes, or you could even create a test-datasize program, that would output the data size from the initupdate_datasize function (or a similar C macro instead.) Depending on your build system it should be possible to pass that parameter as a compiler define.

For example in a Makefile if you're using GNU make this might help:

DATA_SIZE = $(shell ./test-datasize)

CFLAGS = -O3 -Wall -DDATASZ=$(DATA_SIZE)
Snohdo
  • 156
  • 5