5

I have designed what I think is a useful, reusable functionality that I'd like to:

  1. Implement in C/C++ as an open source library; and then
  2. Write different "native binding libraries" for it in various higher-level languages, such that end users writing applications in these languages (Java, Ruby, Python, C#, Haskell, etc.) can all call the same C/C++ code under the hood, but drive that code from inside these libraries.

For example, I might have the following C code:

// Pseudo-code for simple example only, don't read into this too much!
float square(float x) {
    float p;
    p = x * x;

    return p;
}

And then write a "Java binding library" that includes the compiled C code as a native library inside of the JAR, and that exposes a Java API for invoking it:

// Pseudo-code for simple example only, don't read into this too much!
public class SquareManager {
    public SquareManager() {
        super();

        System.loadLibrary("Square");
    }

    public native float square(float x);

    public float calcSquare(float x) {
        return square(x);
    }
}

Ditto for other high-level languages (again, Ruby, Python, etc.).

When I hear people talking about writing C/C++ code, I often hear them talk about targeting various platforms. By this, I assume they mean that, with C/C++, you have to compile the code into binaries that can run on various OS/instruction set combos. For instance, you might have one binary distribution for running on Windows/x86. You might have another one for running on Linux/x86. You might also have a distribution for running on Linux/ARM. So to begin with, if the above statement is inaccurate, please begin by correcting me!

Assuming I'm more or less correct there, then it seems to me that I should be able to:

  • Just write this C/C++ code once; and then
  • Just make sure, for each platform (OS/instruction set combo) that I want to target, that I have a compiler running on my machine that can compile that C/C++ code into a binary that can run natively on the targeted platform

This would be opposed to what I'm concerned about, which would be a situation where:

  • For some reason, I need to write a different version/flavor of the C/C++ code for each targeted platform; and then
  • Compile each version of the source code into a binary that can run on the intended platform

So I ask: Am I correct here, thinking that I can write the C/C++ code one time, and then simply compile it (probably using different compilers, or different compiler configs) multiple times, one time for each targeted platform I want to support? And if I'm incorrect or misled, here, then how?

smeeb
  • 4,820
  • 10
  • 30
  • 49
  • The original purpose of creating C was to make it portable between machines and operating systems. – Rob Mar 30 '17 at 13:57
  • (1) For purely computational code (no I/O, no OS calls), yes. (2) Running compilers on your machine: some compilers can; some cannot. Android NDK can compile ARM and MIPS code by running compilers on Windows, Linux, and Mac. Unmodified GCC, Clang? It gets much harder. (3) Certain platforms e.g. "Windows app store" require you use their (Visual Studio something) compiler; you can't use another compiler. – rwong Mar 30 '17 at 13:59
  • Take a look at OpenCV source code to get a taste of what kind of C++ code could be cross-platform. In general, use of preprocessor directives are necessary. – rwong Mar 30 '17 at 14:01
  • You mention C/C++ but your question is tagged with C: also keep in mind that the two languages typical diverge in terms of linking (e.g. name mangling) for each OS and compiler. C++ also has different compliance levels across platforms, making portability difficult at times (search SO for questions about "this code works in GCC, but breaks in clang" or "this code compiles fine in VC++ and clang, but behaves differently"). I would focus on one or the other, or make it more explicit (with examples) that both are relevant. –  Mar 30 '17 at 15:22

4 Answers4

11

If you are careful about writing platform-neutral code, you might be able to write one C++ version and just compile it several times. I would not take any bets that this would be 100% successful.

What is more common is that 90% of the code is platform neutral and the rest ends up in sections that are controlled by conditional compilation directives, set up to include or exclude different versions of source code depending on the platform. Sometimes these sections are very short.

Why would they be needed? Windows APIs don't exist in Linux and Linux APIs don't exist in Windows, for example. There are differences in the way the O/Ss schedule threads and manage memory, so there may be subtle differences in the way you manage your locks or your handles. If you have any union structures, they may need to be re-arranged depending on endianness. Etc.

I have seen product teams eventually decide to split their code bases just because there were so many exceptions. And I have seen other teams try to merge them again. I don't think there is any perfect solution. But there is no harm in starting out your effort with a single code base, just to see if it is possible.

John Wu
  • 26,032
  • 10
  • 63
  • 84
  • Thanks so much @John Wu (+1) - can you confirm that my understanding of "platform" as an OS/instruction set combo is correct? I just want to make sure that I use the term correctly in my documentation! Thanks again! – smeeb Mar 30 '17 at 13:54
  • Yes that is a pretty good description. – John Wu Mar 30 '17 at 16:42
4

The answer is: Maybe.

There are really two aspects to your question.

C++ Language

As long as you target a particular version of the C++ specification (say, C++ 11) and find a compiler on each platform that supports it, everything should just work (in theory).

The reality is that compilers can have bugs in them or may interpret the specification slightly differently so it's possible that your C++ code may compile correctly with one compiler but not another.

Consuming platform specific libraries

The challenge in cross-platform development comes when you start writing real world code that needs to use things not covered in the C++ specification (like sockets). This code is going to be different for each platform.

There are cross-platform libraries that are available to do these things for you (boost and QT are two examples I know of). You just need to make sure that they support the same compilers and platforms that you want to target.

Ultimately what you wind up is the following:

  • Your C++ source code
  • Third party cross-platform library
  • A build system for each platform you target (Visual Studio project files, make files, etc)

Even if you write C++ code exactly to the C++ specification and find a cross-platform library that supports all of the platforms you want to target, you will still inevitably have to write some platform specific code to cover one of the following:

  • Different interpretations of the C++ Standard
  • Bug in the cross-platform libary
  • Differences in behavior on one platform vs another when calling specific library functions

The platform specific code is usually written using a pre-processor directive so that you can have a single set of source code that compiles for all platforms.

17 of 26
  • 4,818
  • 1
  • 21
  • 25
  • 1
    Two nit-picks: 1) C++11 introduced threading and many synchronisation facilities and atomic ops into the Standard Library, but it's true socket programming is not addressed by the C++ Standard; 2) bugs in modern compilers and OS libs are seriously rare, and the chance the OP will run in any in a one-man project using relatively mundane language features (anyone asking this question won't be writing the next boost spirit or TMP library) is insignificant. – Tony Mar 30 '17 at 14:18
  • I mentioned the bugs because I actually have personally experienced it (granted, it was 20 years ago on IBM AIX). And it was an issue with vanilla C++, not anything complicated. – 17 of 26 Mar 30 '17 at 14:20
  • Yeah - bugs were common back then, albeit far more rarely in the compilers than in the Standard Library implementations. Still not a significant issue with modern compilers. Your warning about behavioural differences is more on the money - for example, Windows sockets loosely impersonates the BSD API, but there are subtle differences that could easily be overlooked, such as the meaning of the error vs. exception flags set by `select()`. – Tony Mar 30 '17 at 14:27
  • 1
    I still have nightmares about those early STL implementations. – 17 of 26 Mar 30 '17 at 14:47
  • Change "bugs" to "different interpretations of the C++ Standard" and you have a much higher hit rate. Basically, it is rare that applications can be ported between different versions of compilers let alone different compilers without hitting some glitch in either compiler errors reported or different behavior unless the code has already been through that process. – Dunk Mar 30 '17 at 15:39
  • Different execution of unspecified behaviour. int x = f() + g(); It is not specified whether f() or g() is called first. If this makes a difference then you have a serious bug. But on one compiler f() might always be called first, and you never notice the bug. Then you use a different compiler and suddenly g() gets called first and the behaviour of your program changes. – gnasher729 Jun 21 '22 at 22:02
1

Am I correct here, thinking that I can write the C/C++ code one time, and then simply compile it (probably using different compilers, or different compiler configs) multiple times, one time for each targeted platform I want to support? And if I'm incorrect or misled, here, then how?

It really depends on the functionality you want to provide. Neither C nor C++ provide much in the way of standard library supprt for anything involving graphics, sound, networking, file management, interprocess communications, etc., so you have to rely on platform-specific extensions or third-party tools to support that. That means you will likely need to write multiple versions of your C or C++ source code to target different platforms.

There are other issues to worry about, as well. The C and C++ specifications are deliberately loose in places to allow implementors to target wide varieties of hardware, from mainframes to microcontrollers. Primitive types sizes (char, int, short, long, float, etc.) are not fixed - the standards mandate that they support at least a minimum range, but they may be wider than that range requires.

For example, the C language standard mandates that an int be able to represent at least the range [-32767...32767], meaning it must be at least 16 bits wide. However, the intent with int is that it maps on to the native word size, meaning that on most hardware built since 1990, an int is 32 bits wide. However, you'll find oddballs that still use a 16-bit int, and there's at least one old platform that used a 36-bit word size, but odds are you'll never see it.

To be safe, you should only assume that the minimum ranges are supported for any given type.

C doesn't mandate a specific endianness - if you're mucking around with serialization/deserialization, you'll have to take into account whether the platform is big- or little-endian.

That's just the stuff off the top of my (very groggy) head. There's more than I'm not thinking of.

TL/DR - you may be able to write a single version of your C or C++ source code and just compile it for each different target, but I wouldn't count on it.

John Bode
  • 10,826
  • 1
  • 31
  • 43
  • "For example, the C language standard mandates that an `int` be able to represent *at least* the range `[-32767...32767]`, meaning it must be *at least* 16 bits wide." – Does it? I thought it only guaranteed that a `char` can hold at least 256 values and that a `int` may not be smaller than a `short`, a `short` not smaller than a `byte`, and a `byte` not smaller than a `char`. A C implementation where `char`, `byte`, `short`, `int`, `log`, and `long long` are all 8 bits is perfectly standards-compliant, at least that's what I thought. The standards lawyers over at [so] may know more. – Jörg W Mittag Mar 30 '17 at 16:29
  • @JörgWMittag: C 2011 [online draft](http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf), 5.2.4.2.1/1: "The values given below shall be replaced by constant expressions suitable for use in `#if` preprocessing directives ... *Their implementation-defined values shall be equal or greater in magnitude (absolute value) to those shown, with the same sign.* ... **`INT_MAX +32767`** ... **`LONG_MAX +2147483647`** ... ", etc. – John Bode Mar 30 '17 at 16:37
  • Interesting. I guess that's new in C11? – Jörg W Mittag Mar 30 '17 at 16:39
  • 1
    @JörgWMittag: No, that's been the case since C89 (don't have a link to that one handy, but the [C99 online draft](http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf) says the same thing). – John Bode Mar 30 '17 at 16:39
  • @JörgWMittag: Now, a C implementation where `char`, `short`, `int`, `long`, and `long long` are all *64 bits wide* would be standards-compliant. – John Bode Mar 30 '17 at 16:42
  • 1
    Found it in C89, too: http://port70.net/~nsz/c/c89/c89-draft.html#2.2.4.2 Apparently, everything I ever knew about C (which wasn't much to begin with, TBH) was a lie! BTW, I think there's some DSPs where that is actually true. – Jörg W Mittag Mar 30 '17 at 16:48
0

Based on the applications I have worked on and written in C/C++ it is possible to do between 50% - 90%.

If it is a command line utility or a server, then you could probably hit 90%. Obviously, you need to target which variant of the language you need to use. For example if you are targeting a word oriented machine (Sperry and Burroughs built a few) that only has a K&R C compiler, you will have to use K&R C and use the backward compatible features of ANSI C (i.e., use K&R declarations and definitions and don't do any implicit sign conversions). Generally the older standards have better support for more systems as they are just older.

For UI work, it is going to shift downward towards the 50%. If you use a cross platform GUI library (WxWidgets or qt) then it should shift closer to 90%.

When you start considering which way to go, take some example code and compile it on the various systems to try to see what will work for you. Furthermore, run unit tests on each of the system regularly as you are developing the code.

Robert Baron
  • 1,132
  • 7
  • 10