3

I'm comparing Numerical Recipes four1.c to Nayuki's FFT. Both are the C versions, but I'm using a C++ driver. For the comparison, I'm compiling (or more appropriately, linking) both into an executable with CL.exe and g++. The two appear to be fighting over whether or not to use extern "C" for the four1 function, but neither seem to care about Nayuki's. I made a header file for four1 that checks for _WIN32 in order to switch appropriately, and it works, but seems to be a completely unacceptable hack. How would I fix this?

Here's the header file:

#pragma once
#ifdef _WIN32
extern "C" void four1(float data[], unsigned long nn, int isign);
#else
void four1(float data[], unsigned long nn, int isign);
#endif

This is what CL does without the extern "C":

drvr.obj : error LNK2019: unresolved external symbol "void __cdecl four1(float * const,unsigned long,int)" (?four1@@YAXQAMKH@Z) referenced in function _main
drvr.exe : fatal error LNK1120: 1 unresolved externals

And this is what g++ does if extern "C" is used:

/tmp/ccK1Hb2N.o: In function `main':
drvr.cpp:(.text+0x347): undefined reference to `four1'
collect2: error: ld returned 1 exit status

What works for CL doesn't work for g++, and what works for g++ doesn't work in CL. At least for this file. No such problem exists for the Nayuki code.

I tried revising the header file as advised, so now here's dfour1.h:

#pragma once
/* C++ needs to know that types and declarations are C, not C++.  */
#ifdef  __cplusplus
# define __BEGIN_DECLS  extern "C" {
 void dfour1(double data[], unsigned long nn, int isign);
# define __END_DECLS    }
#else
# define __BEGIN_DECLS
# define __END_DECLS
#endif

g++ is fine with it. CL isn't.

>cl drvr.cpp dfour1.c fft.c carrier.cpp
Microsoft (R) C/C++ Optimizing Compiler Version 19.00.24215.1 for x86
Copyright (C) Microsoft Corporation.  All rights reserved.

drvr.cpp
Generating Code...
Compiling...
dfour1.c
fft.c
Generating Code...
Compiling...
carrier.cpp
Generating Code...
Microsoft (R) Incremental Linker Version 14.00.24215.1
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:drvr.exe
drvr.obj
dfour1.obj
fft.obj
carrier.obj
drvr.obj : error LNK2019: unresolved external symbol "void __cdecl dfour1(double * const,unsigned long,int)" (?dfour1@@YAXQANKH@Z) referenced in function _main
drvr.exe : fatal error LNK1120: 1 unresolved externals

Incidentally, the same happens if there is NO header file, and I simply add:

extern "C" void dfour1(double data[], unsigned long nn, int isign);

to the drvr.cpp file, and remove the header file. CL only works when extern "C" is there, but then g++ doesn't. Whereas removing extern "C" works with g++ but not with CL.

And yes I know the original header file was wrong, that was the point of the question. When I did it "correctly" it didn't work, thus the post. When I made an "incorrect" header file that checks for which compiler was being used, it worked, and that was the annoying part. Simply checking for c++ does not work.

Nayuki
  • 17,911
  • 6
  • 53
  • 80
KyleG
  • 143
  • 7
  • 4
    The conditional compilation check is wrong, it should check for `__cplusplus` instead. – Some programmer dude Jan 25 '19 at 17:05
  • And of course, you need to link with the library or object file where the function is actually defined. – Some programmer dude Jan 25 '19 at 17:06
  • The differences you are seeing are probably because one compiler is treating your code as C the other as C++. Sort that issue out, and use __cplusplus as already stated. – john Jan 25 '19 at 17:07
  • 4
    `exterm "C"` is for when you want to present a C compatible interface, C calling convention and without C++ name mangling - this obviously restricts what you can expose in the interface (like, no C++ standard library types, no overloading, etc). If that's what you need/want then use it otherwise don't. Simple as that. – Jesper Juhl Jan 25 '19 at 17:09

2 Answers2

7

Look at almost any system C header file and you will see code like:

/* C++ needs to know that types and declarations are C, not C++.  */
#ifdef  __cplusplus
# define __BEGIN_DECLS  extern "C" {
# define __END_DECLS    }
#else
# define __BEGIN_DECLS
# define __END_DECLS
#endif

__BEGIN_DECLS

// some declarations

__END_DECLS

This is the pattern to follow in your own headers where you need to interface C and C++. Except, of course, you shouldn't use leading underscores, as they are reserved for system stuff.

Gem Taylor
  • 5,381
  • 1
  • 9
  • 27
  • 1
    Addendum: [A discussion of the rules of Underscore use in C++.](https://stackoverflow.com/questions/228783/what-are-the-rules-about-using-an-underscore-in-a-c-identifier) – user4581301 Jan 25 '19 at 18:41
1

Use gcc to compile four1.c, not g++.

Nikos C.
  • 50,738
  • 9
  • 71
  • 96