0

Situation

I'm using min GW compiler:

>bin\cpp --version
cpp.exe (GCC) 6.1.0
Copyright (C) 2016 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

I have two header files main.h and submodule.h. For various reasons I cannot simply include one of this headers into the other.


[update]

I think you need to explain the various reasons why you cannot simply include one of this headers into the other because that's the obvious answer... – Andrew

  • I cannot import main.h into submodule.h because in that case a change in main.h would trigger a recompilation of the submodule althoug nothing changed here. Compile time is a major concern for my client.

  • I cannot include submodule.h into main.h because submodule.h defines lots of stuff but only a few definitions are public. My client wants to reduce visibility of identifiers as much as possible.

  • My client uses the content of main.h to verify compatibility of different versions of the target software. Existence and size of the mentioned array is one of the compatibility criteria. Therefore the definition of the array must stay in main.h

  • There are some versions of the target software that do not have the submodule at all. Therefore the files building this submodule may or may not be present. There is a lot of overhead (for my client) to deal with that situation which has to be done by someone else, not me. So my client also wants to limit the number of "flickery" files.


I also have lots of other *.h files that include main.h but not submodule.h, and they should not to hide some things in the sub module.

The submodule.h defines lots of stuff implemented in submodule.c. Among that is an array type definition and a global variable of that type:

typedef const char INDEX_TABLE_t[42]; 
const INDEX_TABLE_t INDEX_TABLE;

The submodule.c implements this array:

const INDEX_TABLE_t INDEX_TABLE {/* 42 random char values */};

The variable INDEX_TABLE ist used in that other *.h files:

char SOME_OTHER_INDEX[23] = {/* 23 random char values */};

#define SELECTOR_VALUE 5
#define a_fix_name INDEX_TABLE[SOME_OTHER_INDEX[SELECTOR_VALUE]]

these *.h files include main.h but not submodule.h.

Therefore I used to add the (exact same) type definition of INDEX_TABLE_t and INDEX_TABLE_t to main.h which compiles fine.

Problem

My client uses a code alaysis tool (QA-C) that complains about the doubled definition of the type INDEX_TABLE_t.

[C] More than one declaration of 'INDEX_TABLE_t' (with no linkage). 

The client instructed me to change the code so that this error will no longer issued by the code analysis tool.

I usually solve this by adding the extern keyword to all but one occurrence. But in this case the compiler throws an exception:

error: conflicting specifiers in declaration of 'INDEX_TABLE_t'

But the declaratios are equal (they are rendered based on a model).

Questions

Do I have any chance to make both happy, the compiler and the code analyser?

Is createing another header file to be included in in main.h or all the other *.h files my only option?

Community
  • 1
  • 1
Timothy Truckle
  • 15,071
  • 2
  • 27
  • 51
  • 4
    I think you need to explain the *various reasons* why you *cannot simply include one of this headers into the other* because that's the obvious answer... – Andrew Jan 08 '19 at 15:17
  • 1
    Double definition is bad. You need to fix that, rather than what you asked. – Eugene Sh. Jan 08 '19 at 15:22
  • 1
    You don't show your code, but did you by chance also add the `extern` keyword to the `typedef`'s? If yes, remove them from there – Ctx Jan 08 '19 at 15:22
  • 2
    I think in a header file you should always use `extern` with variable declarations. Otherwise you would define the variable in every source file that includes this header. – Bodo Jan 08 '19 at 15:24
  • @Bodo This is not a problem if there is no initializer (tentative definition) – Ctx Jan 08 '19 at 15:25
  • 1
    Not so fast, @Ctx. Yes, a variable declaration without either `extern` or an initializer is only a tentative definition, but that's a far cry from *not* a definition. A tentative definition serves as a regular definition (with initializer 0) if there is no other definition or external declaration of the same identifier elsewhere in the translation unit. (C2011, [6.9.2/2](http://port70.net/~nsz/c/c11/n1570.html#6.9.2p2)) – John Bollinger Jan 08 '19 at 15:55
  • 1
    The proper solution is this: don't hide arrays behind typedefs. Don't use global variables. Use setters/getters. However, sharing `const` lookup tables is less ugly, but still there is rarely ever a reason to do so. If you forked up the cash for QA-C then it isn't just some hobbyist project, but you probably have some manner of coding standard to adhere to, yes? Just follow it, it won't allow this code. – Lundin Jan 08 '19 at 16:32
  • @JohnBollinger Cut your teaching tone, where did I write something else? – Ctx Jan 08 '19 at 17:49
  • @Ctx, you wrote something else where you responded to Bodo with "This is not a problem if there is no initializer (tentative definition)". On the contrary, however, it ***is*** a problem to omit `extern` in the declaration of a variable in a header, including when there is no initializer, just as Bodo described. Absent additional code to prevent it, that does produce a definition of the same variable in each translation unit in which the header in question is included. – John Bollinger Jan 08 '19 at 19:34
  • @JohnBollinger No, this is _not_ a problem, this is why this concept exists! You indeed do not seem to have it understood completely, which surprisew me. – Ctx Jan 08 '19 at 20:05
  • Well then, @Ctx, I guess this is your opportunity to teach *me*. In particular, you can teach me how the standard's provisions that "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***" (emphasis added) means something other than what I said. – John Bollinger Jan 08 '19 at 20:11
  • @JohnBollinger Try it out and put `int foo` in two source files and link them together... – Ctx Jan 08 '19 at 20:17
  • @Ctx, compilers do not necessarily conform perfectly by default. GCC, for example, merges duplicate definitions of global variables unless you specify the `-fno-common` option to obtain conforming behavior in this particular area. When I do so, I easily get it to emit a duplicate definition error at link time by using a header that declares a variable without an explicit `extern` specifier. – John Bollinger Jan 08 '19 at 20:24
  • @JohnBollinger Yes, and I am sure there is a whole load of other compiler options you can use to get compile errors of various kinds... This is hilarious. – Ctx Jan 08 '19 at 20:26
  • @Ctx, the point is that the compilation experiment in no way contradicts me. Moreover, even if the compiler performs such a merging, which in truth is a perfectly reasonable manifestation of the attending UB, it can still cause problems in practice when there is a non-tentative definition of the variable somewhere with nonzero initializer. The bottom line is headers that declare variables without using the `extern` specifier sets the stage for non-conforming programs. This *is* a problem. And if it depends on compiler options whether it manifests as an error then it is a *worse* problem. – John Bollinger Jan 08 '19 at 20:35
  • And no, addressing that is not at all the purpose served by tentative definitions. Tentative definitions allow multiple declarations of a variable *in the same translation unit* not to conflict, as long as no more than one has an initializer. It has nothing to do with definitions (and declarations) across multiple translation units. – John Bollinger Jan 08 '19 at 20:37
  • @JohnBollinger The tentative definition directly leads to the generation of a common symbol, which is the intention behind the whole construct. The linker and symboltype part of the whole thing is outside the scope of the standard, this is why the standard wouldn't even theoretically be capable of defining this without heavily extending its scope on linkers and binary formats, which will never happen. But the intention remains and all mainstream compilers adapted this behaviour. – Ctx Jan 08 '19 at 20:52
  • @Ctx, C explicitly specifies that there shall not be more than one external definition of any identifier anywhere in an entire program, i.e. across all contributing translation units. It is irrelevant that the mechanism for combining multiple TUs into a single program is unspecified, because the restriction is defined in terms of the semantics of the C code of the translation units involved. – John Bollinger Jan 08 '19 at 21:11
  • What's more, [the C99 rationale document](http://www.open-std.org/jtc1/sc22/wg14/www/C99RationaleV5.10.pdf) speaks directly to the purpose of the tentative definition concept: "Prior to C90, implementations varied widely with regard to forward referencing identifiers with internal linkage (see §6.2.2). The C89 committee invented the concept of tentative definition to handle this situation." It's about supporting forward declarations of variables. "For the sake of consistency, the same rules apply to identifiers with external linkage, although they're not strictly necessary." – John Bollinger Jan 08 '19 at 21:20
  • @Andrew please see my update in the question. – Timothy Truckle Jan 09 '19 at 08:17
  • @EugeneSh. please see my update in the answer. The code is generated and the code that generates this statements is the same for both places. So duplication is less of a problem as usual. – Timothy Truckle Jan 09 '19 at 08:18
  • do not declare instances of data in header files. only declare `#define` and `enum` and prototypes and `struct' definitions. Always place the actual data instances inside some *.c file. We could help you a lot more if you had actually posted the problem code (headers and C files). in general, use setter/getter functions to access data and place the actual data so it is invisible to all but the setter/getter functions – user3629249 Jan 09 '19 at 23:07

1 Answers1

3

I have two header files main.h and submodule.h. For various reasons I cannot simply include one of this headers into the other.

Then do yourself a favor and fix that, even if you don't actually #include "submodule.h" inside main.h. Your claim that you cannot do so has very bad smell.

The submodule.c implements this array:

const INDEX_TABLE_t INDEX_TABLE {/* 42 random char values */};

You appear to have omitted an = before the initializer. Also, with INDEX_TABLE_t being an array type with const elements, I don't think the extra const there has any additional effect.

My client uses a code alaysis tool (QA-C) that complains about the doubled definition of the type INDEX_TABLE_t.

[C] More than one declaration of 'INDEX_TABLE_t' (with no linkage). 

I suppose the tool is concerned by exactly the fact that the declaration is repeated in separate files, instead of being centralized in a single header. This is a valid concern, not so much for the program now, but for ongoing maintenance and development. You have set a trap for a future maintainer (maybe future you) wherein they might change only one of the type definitions, or change the two in incompatible ways, thus introducing a subtle but impactful bug.

The client instructed me to change the code so that this error will no longer issued by the code analysis tool.

I usually solve this by adding the extern keyword to all but one occurrence. But in this case the compiler throws an exception:

error: conflicting specifiers in declaration of 'INDEX_TABLE_t'

But the declaratios are equal (they are rendered based on a model).

INDEX_TABLE_t designates a type, not an object or function. It cannot have external linkage (per extern) because it automatically and necessarily has no linkage.

Do I have any chance to make both happy, the compiler and the code analyser?

Yes.

Is createing another header file to be included in in main.h or all the other *.h files my only option?

Not exactly, but you do need to put the type definition in a single header, and have all your sources get it from there, directly or indirectly. One alternative to your idea would be to #include that header directly into your .c files, which probably would require careful management of the order of your #include statements.

But overall, it sounds like your header collection could benefit from some refactoring. As a general rule, each header should (and should be able to) include all the headers needed to provide declarations for identifiers used but not declared within, and no other headers. This is facilitated in part by using include guards in every header. There may be other aspects to making that work if you did not design for it from the beginning, but it certainly can be done.


In response to the edited question

That some builds of the software do not include submodule but (presumably) do use main.h is a strong indication that main.h is the wrong place for a typedef for the type of an object of which only submodule provides an instance. It should go in a header associated with submodule or more broadly with the collection of different sources that all use this attribute of submodule.

Perhaps that header could be submodule.h itself. Perhaps it should be a separate header, say submodule_general.h, which might even be a better place for some of the other stuff now in submodule.h, too. Perhaps there's stuff in submodule.h that doesn't need to be there, and removing it -- possibly in conjunction with converting some objects and functions from external to internal -- would make it more palatable to include submodule.h in more places.

However you split declarations among headers to avoid duplication and serve whatever other objectives you may have, you always have the alternatives of including headers into the sources that need them either directly, or indirectly via other headers.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157