views:

700

answers:

11

I got a comment to an answer I posted on a C question, where the commenter suggested the code should be written to compile with a C++ compiler, since the original question mentioned the code should be "portable".

Is this a common interpretation of "portable C"? As I said in a further comment to that answer, it's totally surprising to me, I consider portability to mean something completely different, and see very little benefit in writing C code that is also legal C++.

A: 

Why do you see little benefit? It's pretty easy to do and who knows how you will want to use the code in future.

anon
+11  A: 

No. My response http://stackoverflow.com/questions/649789/why-artificially-limit-your-code-to-c/650533#650533 has some examples of standards-compliant C99 not compiling as C++; earlier C had fewer differences, but C++ has stronger typing and different treatment of the (void) function argument list.

As to whether there is benefit to making C 'portable' to C++ - the particular project which was referenced in that answer was a virtual machine for a traits based language, so doesn't fit the C++ object model, and has a lot of cases where you are pulling void* of the interpreter's stack and then converting to structs representing the layout of built-in object types. To make the code 'portable' to C++ it would have add a lot of casts, which do nothing for type safety.

Pete Kirkham
Thanks. I realized it was hard to chose a "correct" answer to this question, since I'm obviously biased, but I thought this was the best.
unwind
Just a comment on starting your answer with "No", which applies also to the other two ones which start like that: You give proper evidence of the fact that portable C code may be invalid C++. Yet, one can interpret the question title like this: If I write portable C code, is it a good idea to make it compilable on a C++ compiler? I think this can be answered with "Yes", at least I see no point why it would be bad to do so.
Brian Schimmel
If you have a C compiler, it is a waste of time if you are writing C, can cause bugs ( getting into the habit of putting in extra casts to make a C++ compiler happy will mean you are more likely to perform an invalid cast ), and prevents you using some features of C, especially C99 features. If you are writing portable C89 then you are limited to a smaller set of features, most of which made it into C++, but have to take extra care for portability as there are more parts of the spec which are implementation variable.
Pete Kirkham
+7  A: 

Portability means writing your code so that it compiles and has the same behaviour using different compilers and/or different platforms (i.e. relying on behaviour mandated by the ISO standard(s) wherever possible).

Getting it to compile using a different language's compiler is a nice-to-have (perhaps) but I don't think that's what is meant by portability. Since C++ and C are now diverging more and more, this will be harder to achieve.

On the other hand, when writing C code I would still avoid using "class" as an identifier for example.

Simon Nickerson
+1  A: 

It depends. If you're doing something that might be useful to a C++ user, then it might be a good idea. If you're doing something that C++ users would never need but that C users might find convenient, don't bother making it C++ compliant.

If you're writing a program that does something a lot of people do, you might consider making it as widely-usable as possible. If you're writing an addition to the Linux kernel, you can throw C++ compatability out the window - it'll never be needed.

Try to guess who might use your code, and if you think a lot of C++ fans might find your code useful, consider making it C++ friendly. However, if you don't think most C++ programmers would need it (i.e. it's a feature that is already fairly standardized in C++), don't bother.

Chris Lutz
+2  A: 

No, "portable" doesn't mean "compiles on a C++ compiler", it means "compiles on any Standard comformant C compiler" with consistent, defined behavior.

And don't deprive yourself of, say, C99 improvements just to maintain C++ compatibility.

But as long as maintaining compatibility doesn't tie your hands, if you can avoid using "class" and "virtual" and the the like, all the better. If you're writing open source, someone may want to port your code to C++; if you're wring for hire, you company/client may want to port sometime in the future. hey, maybe you'll even want to port it to C++ in the future

Being a "good steward" not leaving rubbish around the campfire, is just good karma in whatever you do.

And please, do try to keep incompatibilities out of your headers. Even if your code is never ported, people may need to link to it from C++, so having a header that doesn't use any C++ reserved words, is cool.

tpdi
A: 

No, being compilable by C++ is not a common interpretation of portable. Dealing with really old code, K&R style declarations are highly portable but can't be compiled under C++.

As already pointed out, you may wish to use C99 enhancements. However, I'd suggest you consider all your target users and ensure they can make use of the enhancements. Don't just use variable length arrays etc. because you have the freedom to but only if really justified.

Yes it is a good thing to maintain C++ compatibility as much as possible - other people may have a good reason for needing to compile C code as C++. For instance, if they want to include it in an MFC application they would have to build plain C in a separate DLL or library rather than just being able to include your code in a single project.

There's also the argument that running a compiler in C++ mode may pick up subtle bugs, depending on the compiler, if it applies different optimisations.

Andy Dent
+1  A: 

It is definitely common practice to compile C code using a C++ compiler in order to do stricter type checking. Though there are C-specific tools to do that like lint, it is more convenient to use a C++ compiler.

Using a C++ compiler to compile C code means that you commonly have to surround your includes with extern "C" blocks to tell the compiler not to mangle function names. However this is not legal C syntax. Effectively you are using C++ syntax and your code which is supposedly C, is actually C++. Also a tendency to use "C++ convenience" starts to creep in like using unnamed unions.

If you need to keep your code strictly C, you need to be careful.

trshiv
+1  A: 

AFAIK all of the code in classic text The C programming language, Second edition can be compiled using a standard C++ compilers like GCC (g++). If your C code is upto the standards followed in that classic text, then good enough & you're ready to compile your C code using a C++ compiler.

Take the instance of linux kernel source code which is mostly written in C with some inline assembler code, it's a nightmare compiling the linux kernel code with a C++ compiler, because of least possible reason that 'new' is being used as an variable name in linux kernel code, where as C++ doesn't allow the usage of 'new' as a variable name. I am just giving one example here. Remember that linux kernel is portable & compiles & runs very well in intel, ppc, sparc etc architectures. This is just to illustrate that portability does have different meanings in software world. If you want to compile C code using a C++ compiler, you are migrating your code base from C to C++. I see it as two different programming languages for most obvious reason that C programmers doesn't like C++ much. But I like both of them & I use both of them a lot. Your C code is portable, but you should make sure you follow standard techniques to have your C++ code portable while you migrate your C code to C++ code. Read on to see from where you'd get the standard techniques.

You have to be very careful porting the C code to C++ code & the next question that I'd ask is, why would you bother to do that if some piece of C code is portable & running well without any issues? I can't accept managebility, again linux kernel a big code source in C is being managed very well.

Always see the two programming languages C & C++ as different programming languages, though C++ does support C & its basic notion is to always support for that wonderful language for backward compatibility. If you're not looking at these two languages as different tools, then you fall under the land of popular, ugly C/C++ programming language wars & make yourself dirty.

Use the following rules when choosing portability:

a) Does your code (C or C++) need to be compiled on different architectures possibly using native C/C++ compilers? b) Do a study of C/C++ compilers on the different architectures that you wish to run your program & plan for code porting. Invest good time on this. c) As far as possible try to provide a clean layer of separation between C code & C++ code. If your C code is portable, you just need to write C++ wrappers around that portable C code again using portable C++ coding techniques.

Turn to some good C++ books on how to write portable C++ code. I personally recommend The C++ programming language by Bjarne Stroustrup himself, Effective C++ series from Scott meyers & popular DDJ articles out there in www.ddj.com.

PS: Linux kernel example in my post is just to illustrate that portability does mean different meanings in software programming & doesn't criticize that linux kernel is written in C & not in C++.

placidhacker
anon
+11  A: 

The current C++ (1998) standard incorporates the C (1989) standard. Some fine print regarding type safety put aside, that means "good" C89 should compile fine in a C++ compiler.

The problem is that the current C standard is that of 1999 (C99) - which is not yet officially part of the C++ standard (AFAIK). That means that many of the "nicer" features of C99 (long long int, , ...), while supported by many C++ compilers, are not strictly compliant.

"Portable" C means something else entirely, and has little to do with official ISO/ANSI standards. It means that your code does not make assumptions on the host environment. (The size of int, endianess, non-standard functions or errno numbers, stuff like that.)

From a coding style guide I once wrote for a cross-platform project:

Cross-Platform DNA (Do Not Assume)

  • There are no native datatypes. The only datatypes you might use are those declared in the standard library.
  • char, short, int and long are of different size each, just like float, double, and long double.
  • int is not 32 bits.
  • char is neither signed nor unsigned.
  • char cannot hold a number, only characters.
  • Casting a short type into a longer one (int -> long) breaks alignment rules of the CPU.
  • int and int* are of different size.
  • int* and long* are of different size (as are pointers to any other datatype).
  • You do remember the native datatypes do not even exist?
  • 'a' - 'A' does not yield the same result as 'z' - 'Z'.
  • 'Z' - 'A' does not yield the same result as 'z' - 'a', and is not equal to 25.
  • You cannot do anything with a NULL pointer except test its value; dereferencing it will crash the system.
  • Arithmetics involving both signed and unsigned types do not work.
  • Alignment rules for datatypes change randomly.
  • Internal layout of datatypes changes randomly.
  • Specific behaviour of over- and underflows changes randomly.
  • Function-call ABIs change randomly.
  • Operands are evaluated in random order.
  • Only the compiler can work around this randomness. The randomness will change with the next release of the CPU / OS / compiler.
  • For pointers, == and != only work for pointers to the exact same datatype.
  • <, >, <, > work only for pointers into the same array. They work only for char's explicitly declared unsigned.
  • You still remember the native datatypes do not exist?
  • size_t (the type of the return value of sizeof) can not be cast into any other datatype.
  • ptrdiff_t (the type of the return value of substracting one pointer from the other) can not be cast into any other datatype.
  • wchar_t (the type of a multibyte character) can not be cast into any other datatype.
  • Any ..._t datatype cannot be cast into any other datatype
DevSolar
jalf
It's more of a shotgun approach to get people think paranoid. ;-)
DevSolar
Good rules, although has there _ever_ been a system where 'a'-'A' != 'z'-'Z'? Even EBCDIC wasn't that bad...
Mikeage
The point is, do we know all character encodings that ever have, and ever will be, in use? No. Hence, rely on ctype.h, not character arithmetics.
DevSolar
nice list, very paranoid.
Matt Joiner
+1  A: 

FWIW, once a project gains a certain size and momentum, it is not unlikely that it may actually benefit from C++ compatibility: even if it not going to be ported to C++ directly, there are really many modern tools related to working/processing C++ source code.

In this sense, a lack of compatibility with C++, may actually mean that you may have to come up with your own tools to do specific things. I fully understand the reasoning behind favoring C over C++ for some platforms, environments and projects, but still C++ compatibility generally simplifies project design in the long run, and most importantly: it provides options.

Besides, there are many C projects that eventually become so large that they may actually benefit from C++'s capabilities like for example improved suport for abstraction and encapsulation using classes with access modifiers.

Look at the linux (kernel) or gcc projects for example, both of which are basically "C only", still there are regularly discussions in both developer communities about the potential gains of switching to C++.

And in fact, there's currently an ongoing gcc effort (in the FSF tree!) to port the gcc sources into valid C++ syntax (see: gcc-in-cxx for details), so that a C++ compiler can be used to compile the source code.

This was basically initiated by a long term gcc hacker: Ian Lance Taylor.

Initially, this is only meant to provide for better error checking, as well as improved compatibility (i.e. once this step is completed, it means that you don't necessarily have to have a C compiler to to compile gcc, you could also just use a C++ compiler, if you happen to be 'just' a C++ developer, and that's what you got anyway).

But eventually, this branch is meant to encourage migration towards C++ as the implementation language of gcc, which is a really revolutionary step - a paradigm shift which is being critically perceived by those FSF folks.

On the other hand, it's obvious how severely gcc is already limited by its internal structure, and that anything that at least helps improve this situation, should be applauded: getting started contributing to the gcc project is unnecessarily complicated and tedious, mostly due to the complex internal structure, that's already to started to emulate many of the more high level features in C++ using macros and gcc specific extensions.

Preparing the gcc code base for an eventual switch to C++ is the most logical thing to do (no matter when it's actually done, though!), and it is actually required in order to remain competitive, interesting and plain simply relevant, this applies in particular due to very promising efforts such as llvm, which do not bring all this cruft, complexity with them.

While writing very complex software in C is often course possible, it is made unnecessarily complicated to do so, many projects have plain simply outgrown C a long time ago. This doesn't mean that C isn't relevant anymore, quite the opposite. But given a certain code base and complexity, C simply isn't necessarily the ideal tool for the job.

In fact, you could even argue that C++ isn't necessarily the ideal language for certain projects, but as long as a language natively supports encapsulation and provides means to enforce these abstractions, people can work around these limitations.

Only because it is obviously possible to write very complex software in very low level languages, doesn't mean that we should necessarily do so, or that it really is effective in the first place.

I am talking here about complex software with hundreds of thousands lines of code, with a lifespan of several decades. In such a scenario, options are increasingly important.

none
+1  A: 

No, matter of taste. I hate to cast void pointers, clutters the code for not much benefit.

char * p = (char *)malloc(100);

vs

char * p = malloc(100);

and when I write an "object oriented" C library module, I really like using the 'this' as my object pointer and as it is a C++ keyword it would not compile in C++ (it's intentional as these kind of modules are pointless in C++ given that they do exist as such in stl and libraries).

tristopia