1

Why would I want this?

I'd like to use a C package that was initially built in 2007 and last updated in 2016 according to the Changelog. I gather that it would have compiled cleanly back than.

Sadly, this is no longer the case.

The error

Running ./configure and make, I get a Multiply defined error:

gcc  -g -O2   -o laplaafit laplaafit.o multimin.o common.o lib/libgnu.a -lgsl -lgslcblas -lm 
/usr/bin/ld: common.o:/home/<user>/build/subbotools/subbotools-1.3.0/common.c:27: multiple definition of `Size'; laplaafit.o:/home/<user>/build/subbotools/subbotools-1.3.0/laplaafit.c:38: first defined here
/usr/bin/ld: common.o:/home/<user>/build/subbotools/subbotools-1.3.0/common.c:26: multiple definition of `Data'; laplaafit.o:/home/<user>/build/subbotools/subbotools-1.3.0/laplaafit.c:37: first defined here

Specifically, both files (laplaafit.c and common.c) have the declaration

double *Data; /*the array of data*/
unsigned Size;/*the number of data*/

with a definition of both variables following further down in the code in both files (I believe with load(&Data,&Size,infile); which calls function int load() in common.c which reads the array *Data from a file and determines its length Size).

This is what causes the error. The variables are important in both files (removal in either leads to '(variable)' undeclared errors). Moving to a header files would not change anything if the header (say common.h) is included in both .c files.

Edit: Since it was raised in the comments that load(&Data,&Size,infile); is "far from being a definition" I figure I should be a bit more detailed.

load(&Data,&Size,infile);

calls the int load(...) function from common.c

int load(double **data,unsigned *size,FILE *input)

Here, *Data is the array starting in address Data. &Data is the pointer to the pointer (double pointer?) to the start of the array. **data is a double pointer to a local array in load(). If the function obtains &Data for this, data actually refers to the original global array and the program gan write into it by accessing it via pointer *data.

And *size (for which the function obtains &Size) is the value in address &Size so the other global variable.

The function then writes into *data and *size multiple times, e.g., in the very end:

*size=i;
*data = (double *) my_realloc((void *) *data,(*size)*sizeof(double));

If I am not mistaken, this may count as the global variables *Data and Size being defined.

Furthermore, the comment says that I do not actually know enough C to diagnose the program and that I should therefore rather hire someone who does. This would raise the bar for being allowed to post in Stackoverflow to a very high level; a level that is not always attained in the questions that are commonly posted and seen as perfectly acceptable. It may actually be a reasonable suggestion, but it would leave no place for me to ask questions I might have about C or any other language. If the author of the comment is serious about this, it may be worth posting in Meta and suggesting splitting Stackoverflow in two, one for experts, one for everyone else.

How to solve the problem (making the code compile)

As I see it, there are two ways to approach this:

  • Rewrite the software package avoid multiple definitions. I would ideally like to avoid this.
  • Find a way to compile as it would have been compiled between 2007 and 2016. I assume it would have compiled cleanly back then. There are multiple potential problems with this: Would the old compiler still work with my 2021 system? Would that work with the libraries in a modern system? Even if I succeed, will the resulting executable behave as it would have been intended by the authors? Still, this seems the preferable option.

It is also still possible that I misinterpret the error or misunderstand something.

It is also possible that even between 2007 and 2016 this would not have compiled cleanly with my compiler (gcc) and that the authors used a different compiler that accepts multiple definitions.

Solution by compiling with old compiler behavior

Include the -fcommon option as discussed in kaylum's answer below.

Attempt to solve by changing the code

The intended behavior is obviously for the two variables Data and Size in the two files to refer to the same variable (the same point in memory). Therefore, declaring the variable as extern in laplaafit.c should recover the same behavior. Specifically, exchanging

double *Data; /*the array of data*/
unsigned Size;/*the number of data*/

for

extern double *Data; /*the array of data*/
extern unsigned Size;/*the number of data*/

The code compiles cleanly then again. I am not sure how certain I am that the bahavior is actually the same as intended by the authors (and achieved with old gcc versions and recent gcc with -fcommon) though.

Why I think this question is of general interest for programming (and this belongs on Stackoverflow)

I am guessing, however, that the question is more general. There are many old software packages around. Given enough time, most of them will break eventually.

Software

My system is Arch Linux kernel 5.11.2; C compiler: gcc 10.2.0; GNU Make 4.3.

0range
  • 2,088
  • 1
  • 24
  • 32
  • The reason is clearly deeper in the code itself or external libraries. So far, this needs focus and the particular problem. Just omitting the source code might clear the error, but it will also not provide whatr you want: the library. Unless you can provide the specific reason, this is OT here. If you are not familiar building and debugging C code, contacting the author might be your best approach. Maybe there is a more recent fork? – too honest for this site Mar 07 '21 at 03:23
  • 1
    *because a .c file gets included*. How do you reach that conclusion? The `laplaafit_SOURCES` variable does not tell you that. In fact the error tells you something different - It tells you that the same variables are defined in both common.c and laplaafit.c.. And if you just open those files you can easily see the same variables in each file. So nothing to do with includes. – kaylum Mar 07 '21 at 03:23
  • @kaylum To be fair: if the C files would not be there, _that_ problemwould not arise;-) However, that could also be a problem handling global variables between different C files. I think there has been some clarification for the upcoming standard fixing a long-time defect. It might reuire a different linker option if not meant for the same toolchain (if supported by OP's at all). However, finding the places is just the first step and it clearly requires digging deeper. – too honest for this site Mar 07 '21 at 03:27
  • 1
    @toohonestforthissite I'm not saying there isn't an issue. Just pointing out that the diagnosis of the issue is not correct. If we start off with the wrong diagnosis then reaching the right resolution is that much harder. – kaylum Mar 07 '21 at 03:29
  • 1
    You may be able to fix this by compiling with `-fcommon`. From the [gcc manual](https://gcc.gnu.org/onlinedocs/gcc/Code-Gen-Options.html): *The -fcommon places uninitialized global variables in a common block. This allows the linker to resolve all tentative definitions of the same variable in different compilation units to the same object, or to a non-tentative definition. This behavior is inconsistent with C++, and on many targets implies a speed and code size penalty on global variable references. It is mainly useful to enable legacy code to link without errors.* – kaylum Mar 07 '21 at 03:38
  • 1
    The default for older gcc compilers was `-fcommon`. That has been changed to `-fno-common` from gcc 10 (which is what you are using). So either manually add the `-fcommon` or use a pre-10 gcc. – kaylum Mar 07 '21 at 03:40
  • @kaylum You are right. It compiles cleanly with `-fcommon` – 0range Mar 07 '21 at 03:46
  • 1
    Since the question has been closed now, would you please elaborate why? I believe I have followed the recommendations by @toohonestforthissite and by @kaylum on how to improve the question by 1) removing my initial wrong diagnosis and 2) including details (incl. code) on the actual reason for the error, as well as 3) how the error could be addressed by changing the code (declaring variables as `extern` in one file). The question was closed after I made these changes, so I assume there's something else that's wrong with the question? – 0range Mar 07 '21 at 13:44
  • What you call a definition is far away from being one. With all due respect and no intend to offend: if you want to diagnose program code in any language you need to know the language first. Otherwise you should try - as I wrote already - to ask the author for support. Depending on the functionality of that lib, it might even be easier (and/or cheaper) to write it yourself (for which one needs language knowledge, too, but maybe not to that detail level) or hire a freelancer. – too honest for this site Mar 07 '21 at 15:03
  • On a sidenote, after below discussion, I don't think it's really interesting for others. Just another bugged code. If a lib has such basic errors, I would be suspicious what else is there undr the hood. And as it seems abandoned, maintenance is also an issue. (this statement is without knowing the full background story, so quite generic, of course) – too honest for this site Mar 07 '21 at 15:06
  • 1
    I think this is going to be increasingly useful as GCC 10 and its successor versions become more widely used and this bites more and more people. I'm working on the code at work to fix this problem. It ain't trivial in that context (the code isn't as well organized as I'd like — to put it politely). – Jonathan Leffler Mar 07 '21 at 15:28
  • 1
    Not sure why the question is closed as "too broad". While it seems to be lengthy, the problem is specified quire clearly. Voted to reopen. – Tsyvarev Jan 14 '22 at 16:35

1 Answers1

5

Multiple definitions of global variables of the same type with the same name are permitted in gcc if the source is built with -fcommon. From the gcc manual:

The -fcommon places uninitialized global variables in a common block. This allows the linker to resolve all tentative definitions of the same variable in different compilation units to the same object, or to a non-tentative definition. This behavior is inconsistent with C++, and on many targets implies a speed and code size penalty on global variable references. It is mainly useful to enable legacy code to link without errors.

The default of pre-10 gcc used to be -fcommon but that has been changed to -fno-common in gcc 10. From the gcc 10 release notes:

GCC now defaults to -fno-common. As a result, global variable accesses are more efficient on various targets. In C, global variables with multiple tentative definitions now result in linker errors. With -fcommon such definitions are silently merged during linking.

This explains why the build fails in your environment using gcc 10 but was able to build with older gcc versions. Your options are to either add -fcommon into the build or use a gcc version prior to 10.

Or as pointed out by @JohnBollinger another option is to fix the code to remove those multiple definitions and make the code conform strictly to the C standard.

kaylum
  • 13,833
  • 2
  • 22
  • 31
  • 1
    Another option would be to fix the code, which is indeed non-conforming. – John Bollinger Mar 07 '21 at 03:50
  • @JohnBollinger Indeed. Answer updated. – kaylum Mar 07 '21 at 03:54
  • @JohnBollinger As I wrote in my comment, IIRC that is actually a defect in the standard which is to be addressed in C2x as it seems. I can't provide the details, because I just have a faint memory I read something some time ago. Maybe that's even the reason gcc changed the default behaviour. If that's true, I would expect this to break much more code, ecpecially legacies in companies. Will be fun. – too honest for this site Mar 07 '21 at 03:54
  • Ok, checked it. It is already in C11 (n1570). 6.9§5. It seems my memory was too faint and maybe I actually read about the problem in some gcc changelog or bugreport, i.e. the other way 'round. So the bug is really in the code if that's the actual problem. – too honest for this site Mar 07 '21 at 04:10
  • 1
    @toohonestforthissite, yes, the one-definition rule has been in every version of ISO C published to date, all the way back to C90. – John Bollinger Mar 07 '21 at 04:12
  • @JohnBollinger And still there is something nagging in my brain about the behaviour apparently expected by the libs in the question. Could it be different in C++? But then I'm pretty sure it's not. Heck, that bothers me now :-} – too honest for this site Mar 07 '21 at 04:24
  • @toohonestforthissite: C++ is stricter than C. You may be thinking of the common extension (a double entendre here) cited in [Annex J.5.1: Multiple external definitions](http://port70.net/~nsz/c/c11/n1570.html#J.5.11): _There may be more than one external definition for the identifier of an object, with or without the explicit use of the keyword extern; if the definitions disagree, or more than one is initialized, the behavior is undefined (6.9.2)._ It's not the standard; it used to be a common extension, and the double entendre arises because it is closely related to COMMON blocks in Fortran. – Jonathan Leffler Mar 07 '21 at 05:45
  • @JonathanLeffler: Maybe, but it's also the same in N2596 for C2x. I tend to think I read about that J5.11 behaviour as default being a bug in gcc somewhere. Maybe I'm getting old or the lockdown takes it's toll (the first is certain, the second more and more likely). Anyway, i think I'm over it. Thanks. – too honest for this site Mar 07 '21 at 05:53