4
#include <stdio.h>
typedef unsigned char uint8_t;
// I want this at the end of the file
//static const uint8_t hello[] = { 'H','e','l','l','o',' ','W','o','r','l','d','!','\0'};
int main()
{   // how do I declare a forward reference to 'hello' here?
    printf("%s\n", hello);
    return;
}
// but down here, the linker can't resolve it
static const uint8_t hello[] = { 'H','e','l','l','o',' ','W','o','r','l','d','!','\0'};

Error C2065 'hello': undeclared identifier

For cosmetic reasons, I would like to put my large, static data tables at the end of my c source file. But how do I reference it? I have used a forward reference in a function to resolve functions that appear later in the file, but static variables are giving me a headache. I tried extern (as a last hope,) but the linker looks outside of the module (which makes sense,) but won't resolve to the darn variable just a few lines past the function that needs it. Is this a C limitation (I've tried this on two compilers,) or am I just missing something incredibly obvious?

Level 42
  • 400
  • 2
  • 7
  • how about putting those declarations in a header file? – artm Nov 30 '19 at 06:19
  • 1
    Try `static const uint8_t hello[13];` before `main()` – chux - Reinstate Monica Nov 30 '19 at 06:23
  • chux, that works. Interestingly, doing the same thing within the function defines a new private variable. But outside--as you point out--it's treated as a forward. Odd. I was worried that outside the function would define a new variable as well. In any case, that works. thank you. – Level 42 Nov 30 '19 at 06:32
  • [That](https://stackoverflow.com/questions/59113698/how-do-i-reference-a-static-function-in-c-that-appears-after-the-function-that-n#comment104458212_59113698) is a _tentative definition_. I have my doubts that `static const uint8_t hello[];` is valid in standard C. – chux - Reinstate Monica Nov 30 '19 at 06:38
  • Level 42, did the accepted code compile well for you? – chux - Reinstate Monica Nov 30 '19 at 07:17
  • See [Why is this statement producing a linker error with gcc?](https://stackoverflow.com/q/52067353/2410359) – chux - Reinstate Monica Nov 30 '19 at 07:24
  • chux, please promote your comment to an Answer so I may accept it. – Level 42 Nov 30 '19 at 15:25
  • Level 42, Use @ before user name to cause notification to more than OP/answerer. – chux - Reinstate Monica Nov 30 '19 at 16:59
  • You know C lets you initialize char arrays with string literals, right? `uint8_t hello[] = "foo";` seems a lot more convenient than `uint8_t hello[] = {'f','o','o','\0'};` (unless you're trying to support the nonexisting platform where `uint8_t` isn't a character type). – Petr Skocik Nov 30 '19 at 17:15
  • 1
    @chux-ReinstateMonica, thanks for reminding me about '@'. Until recently my reputation was not high enough to use '@name' PSocik, yes I know about string literal initialization, but what I posted more closely resembles the 'data tables' I talk about. I.e., my program does not use string data in the tables. – Level 42 Nov 30 '19 at 17:38

2 Answers2

2

Here you are.

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

static const uint8_t hello[]; 

int main( void )
{
    printf("%s\n", hello);
}

static const uint8_t hello[] = { 'H','e','l','l','o',' ','W','o','r','l','d','!','\0'};

Here

static const uint8_t hello[]; 

is the so-called tentative definition of the array hello.

That is the name hello shall be declared before its usage.

On the other hand, as the name has the internal linkage then you could place its definition

static const uint8_t hello[] = { 'H','e','l','l','o',' ','W','o','r','l','d','!','\0'};

in a header.

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
  • Hmmm, with `static const uint8_t hello[];` and `gcc -std=c11 -O0 -g3 -pedantic -Wall -Wextra -Wconversion -c -fmessage-length=0 -MMD -MP -MF"my_strtol.d" -MT"my_strtol.o" -o "my_strtol.o" "../my_strtol.c"`, I get `error: array size missing in 'hello'`. Are you compiling with standard C or some extension? – chux - Reinstate Monica Nov 30 '19 at 06:33
  • @chux-ReinstateMonica According to the C Standard the array has a composite type at the end of the translation unit. – Vlad from Moscow Nov 30 '19 at 06:40
  • Was your compiler options using standard C when you tested the code? I'd like to see them so I may try to see why mine failed this code. – chux - Reinstate Monica Nov 30 '19 at 06:41
  • @chux-ReinstateMonica I rely on the quote from the C Standard "— If both types are array types, the following rules are applied: • If one type is an array of known constant size, the composite type is an array of that size." – Vlad from Moscow Nov 30 '19 at 06:43
  • C has "If the declaration of an identifier for an object is a tentative definition and has internal linkage, the declared type shall not be an incomplete type." Is not `static const uint8_t hello[];` an incomplete type with internal linkage? – chux - Reinstate Monica Nov 30 '19 at 06:46
  • @chux-ReinstateMonica It seems that this means that the composite declaration shall not ne an incomplete type. – Vlad from Moscow Nov 30 '19 at 06:52
  • I suspect a size is required in `static const uint8_t hello[];` In OP's case 13 or more. – chux - Reinstate Monica Nov 30 '19 at 06:53
  • @chux-ReinstateMonica There is an example of an incomplete array with external linkage. So opposite to incomplete types of tentative definitions with external linkage it seems an object with internal linkage shall have a complete type at the end of the translation unit. – Vlad from Moscow Nov 30 '19 at 06:56
  • I find "An identifier for an object with internal linkage and an incomplete type is declared with a tentative definition (6.9.2)." under "J.2 Undefined behavior". – chux - Reinstate Monica Nov 30 '19 at 06:59
  • @chux-ReinstateMonica Again it can mean that an object with the tentative definition shall not be incomplete at the end of the translation unit. – Vlad from Moscow Nov 30 '19 at 07:02
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/203368/discussion-between-vlad-from-moscow-and-chux-reinstate-monica). – Vlad from Moscow Nov 30 '19 at 07:03
  • 1
    Hi Vlad, I tried your code on two different compilers. VS2005 gives the error Error error C2133: 'hello' : unknown size. And VS2019 gives Error MSB6006 "CL.exe" exited with code 2. When I use the size specifier it works as chux pointed out. At least on my two visual studio compliers the tentative definition requires the size. – Level 42 Nov 30 '19 at 15:17
  • 1
    On a final note: I don't like the brittle forward declaration as defined by C. The problem is that if you rename, or delete the actual definition, your program will compile and link without so much as a warning as what was once the declaration, now auto-magically becomes the definition. Your code will run, and learning your data table(s) are now zero filled my not be intiuitively obvious. – Level 42 Nov 30 '19 at 15:22
  • @Level42 `char foo[];`, without the size, wouldn't work as a tentative definition under pedantic settings, but the concern is very valid otherwise. – Petr Skocik Nov 30 '19 at 17:10
2

I would like to put my large, static data tables at the end of my c source file. But how do I reference it?

Use a tentative definition.

static const uint8_t hello[13 /* more */];

int main(void) {
  ...
}

static const uint8_t hello[] = { 
    'H','e','l','l','o',' ','W','o','r','l','d','!','\0' };

Cites follow. There is reasonable disagreement as to if static const uint8_t hello[]; should work. Compilers I use prohibit it which matches my reading of the spec.

6.9.2 External object definitions A declaration of an identifier for an object that has file scope without an initializer, and without a storage-class specifier or with the storage-class specifier static, constitutes a tentative definition. If a translation unit contains one or more tentative definitions for an identifier, and the translation unit contains no external definition for that identifier, then the behavior is exactly as if the translation unit contains a file scope declaration of that identifier, with the composite type as of the end of the translation unit, with an initializer equal to 0.

If the declaration of an identifier for an object is a tentative definition and has internal linkage, the declared type shall not be an incomplete type. C17 § 6.9.2 2&3

J.2 Undefined behavior An identifier for an object with internal linkage and an incomplete type is declared with a tentative definition (6.9.2).

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256