Kuro5hin.org: technology and culture, from the trenches
create account | help/FAQ | contact | links | search | IRC | site news
[ Everything | Diaries | Technology | Science | Culture | Politics | Media | News | Internet | Op-Ed | Fiction | Meta | MLP ]
We need your support: buy an ad | premium membership

Code reuse and C++

By Leimy in Technology
Sat Mar 10, 2001 at 09:41:19 AM EST
Tags: Software (all tags)

C up until the recent C99 standard has been a subset of the C++ language. C++ does however support many different programming paradigms such as generic template programming and OOD. Modular code is desirable for debugging, quick developement and ease of reuse. How can we use C++ to create C callable interfaces to our objects and classes and thereby increase the amount of code reuse possible?

C++ supports function overloading which allows two or more functions with the same name to be stored in compiled objects with different signatures. This is achieved through name mangling. Below is an example of a function and its output on Linux with gcc:

void func () {}

00000000 T func
00000000 t gcc2_compiled.

And now the same function in C++:

test.o (from test.cpp):
00000000 ? __FRAME_BEGIN__
00000000 T func__Fv
00000000 t gcc2_compiled.

As you can see the name of the symbol stored in the object file is different. This would make calling functions written in C++ from C impossible if it wasn't for the extern keyword. This allows us to specify that the code in question, whether it is a function or variable, is defined in some other object. If we use the specific extern "C" {} block we can tell the compiler to generate code such that the linker can look up the functions stored within the block with C-style linkage.

Let's see what happens to our C++ object code when the func() function is declared with extern "C".

extern "C" void func () {}

00000000 ? __FRAME_BEGIN__
00000000 T func
00000000 t gcc2_compiled.

Now you can see that the symbol in the object file is the same as it would have been if it was compiled as C code.

So how does one write a C interface to say an object and its member functions? Below is a coded example:

class Info
void dump ();//do some output

typedef void * CInfo;

extern "C"
void create_info (CInfo *);
void destroy_info (CInfo *);
void dump_info (CInfo);

#include "info.h"
#include "cinfo.h"

//C++ definition of dump method
void Info::dump ()
//dump data

void create_info (CInfo * inf)
Info * temp = new Info;
*inf = reinterpret_cast <CInfo> (temp);

void destroy_info (CInfo * inf)
Info * temp = reinterpret_cast <Info *> (*inf);
delete temp;

void dump_info (CInfo inf)
Info * temp = reinterpret_cast <Info * > (inf);

While this isn't the most useful of examples we now have a fully functional C++ object with a member function and a C callable interface to the whole thing. Below is the dump of the final object info.o:

00000000 ? __FRAME_BEGIN__
________ U __builtin_delete
________ U __builtin_new
00000014 T create_info
00000038 T destroy_info
00000000 T dump__4Info
00000058 T dump_info
00000000 t gcc2_compiled.

As you can see we now have two functions named dump one named dump__4Info which is the member function dump of the class Info and another called dump_info which is the C callable symbol.

Note this is but one solution to the problem as I am sure many people will point out in their comments below. Please comment! :)


Voxel dot net
o Managed Hosting
o VoxCAST Content Delivery
o Raw Infrastructure


Was this a good article?
o Very Informative! 17%
o Good but I would do that differently. 17%
o I already knew that but thanks anyhow. 29%
o Ahhh... this crap ain't no good. 35%

Votes: 51
Results | Other Polls

Related Links
o Also by Leimy

Display: Sort:
Code reuse and C++ | 44 comments (8 topical, 36 editorial, 0 hidden)
Don't use typedef void* for abstract datatypes (4.62 / 8) (#23)
by Per Abrahamsen on Wed Mar 07, 2001 at 12:09:08 PM EST

[ I posted this comment to the previous version of the story too... ]

By using "void*" for your "opaque pointers", you lose all typechecking, since void* is compatible with almost everything in C, and because typedef doesn't create a new type in C or C++, just a new name for an old type.

Instead, use forward declared structs...

/* Declare foo opaque type */
struct foo;
foo* foo_create (void);
void foo_manipulate (foo* f);
void foo_delete (foo* f);

/* Declare bar opaque type */
struct bar;
bar* bar_create (void);
void bar_do_it (bar* b);
void bar_delete (bar* b);

/* use them */
foo* f = foo_create (); /* ok */
bar* b = foo_create (); /* error */
foo_manipulate (f); /* ok */
bar_do_it (f); /* error */
foo_delete (f); /* ok */
bar_delete (f); /* error */

If you use struct forwards, all the errors will be caught by the compiler, if you use typedef void* the will not be caught.

To other minor style issues with the code in the story: If you make your constructor return the object, you can use it directly when declaring variables of the type:

foo* f = foo_create ();

instead of

foo* f;

The library user save a line, and we avoid having a point in the code where "f" is uninititalized.

Also, it is a bad idea to hide the fact that it is a pointer with a typedef. This is because the type has pointer semantics, hiding the pointer can make it easier to assume value semantics by accident.

If the abstract datatype is implemented in C++, all the accessor functions (foo_create, foo_manipulate, foo_delete) should obviously be declared extern "C" for use in C code.

Ahhh.... (3.00 / 1) (#37)
by Leimy on Sat Mar 10, 2001 at 11:34:56 AM EST

Apparently your kung fu is better than mine! :)

That is something I didn't think of but would indeed be useful. Since the scope of my code/project only dealt with the existence of one of these interfaces for C, I didn't think of it.

Also if I bother to have a

struct foo * F;

and then call

F = bar_create();

Shouldn't I be slapped anyway for being foolish enough to call bar_create on a foo type?

The more you do automagically for the programmer the sloppier the programmer will become. This is why I think that Java's garbage collection is pretty great but can be detrimental to beginning programmers. Memory leaks are almost impossible but what does the seasoned Java programmer do when he gets to C...? Chances are he won't free malloc'd memory all the time because he was trained that he didn't have to.

I understand the value of the programmer not having to worry about everything and that that is the very reason we have libraries of code.

I guess I just believe that people calling algorithms should have an understanding of what they are first.
Wrong numbers are never busy
[ Parent ]
CT vs RT (3.00 / 1) (#38)
by erikn on Sat Mar 10, 2001 at 01:15:24 PM EST

Shouldn't I be slapped anyway for being foolish enough to call bar_create on a foo type?
I guess I just believe that people calling algorithms should have an understanding of what they are first.
Basically, sure. But not every function is named bar_create. And maybe your foo type becomes a bar someday. What's wrong with making a runtime error a compile-time error? I'll give you that the best possible learning experience is to make an error and then track it down at runtime. You get your nose rubbed in it, and learn more things just by dint of working it out. ... but ... not every bug gets found. Not every code path is executed. So let the compiler do the heavy lifting and lift your hand to slap yourself when you see the CT error. :-) Robust code and elitism are at odds, here, imho.


[ Parent ]

I see the point. (3.00 / 1) (#39)
by Leimy on Sat Mar 10, 2001 at 01:43:26 PM EST

If there is a way to make a compiler force robustness then its a good idea to use the struct based solution in production code. Catching errors early in production code is way more important than the academics of the situation. To learn more about why struct is better than a void * in this case is to try it both ways to see what happens. I am glad that the struct alternative (which is IMHO better than my original code) was presented.

It brought out two different perspectives on error checking the use/misuse of interfaces. My original is good for educational purposes of what is minimally needed to get the job done. The alternative is a much better approach.

Wrong numbers are never busy
[ Parent ]
Yes, but... (none / 0) (#43)
by beergut on Tue Mar 13, 2001 at 10:07:04 AM EST

There is a problem with this, however, in that it becomes difficult/non-threadsafe to do error checking when you use a construct like:

foo* f = foo_create ();

Instead of:

foo* f;
int err;
if ((err = foo_create(&f))) {...}

The problem is, do you use errno, or maybe your own global library error variable? If you choose to use a global error variable, do you have namespace contention issues later? Also, while errno is, in many cases, a macro that dereferences a pointer into thread-specific data areas in a multi-threaded program, will your global error value work the same way everywhere?

The solution that I've started using is this one: define errors in an enumeration in the library header file, grokkable by C and C++, then have all interface functions that could cause an error return an integer (or, typedef the error enumeration, and have it return that).

This helps to maintain thread safety (even if you don't use it now, factor for it in the future). It also provides a consistent, orthogonal interface to your library, and makes it easier to track down the cause of errors at runtime.

i don't see any nanorobots or jet engines or laser holography or orbiting death satellites.
i just see some orangutan throwing code-feces at a computer screen.

-- indubitable
[ Parent ]

C hasn't ever been a subset of C++ (2.00 / 1) (#40)
by pfaffben on Sat Mar 10, 2001 at 10:46:19 PM EST

C, even C89, has never been a subset of C++. Try this valid C89 program in your C++ compiler:

#include <stdlib.h>

int main (void)
  const int *new = malloc (sizeof *new);
  return 0;

That's valid (though useless) C89 but invalid C++ for at least three reasons.

Point taken. Try this: (none / 0) (#41)
by Leimy on Sun Mar 11, 2001 at 01:22:22 PM EST

#include <stdio.h>
struct A {};

int main ()
printf("%d", sizeof(struct A));

return 0;
The sizes are even different between C and C++. Probably due to the fact that structs and classes in C++ are the same thing with different default permissions. When C++ creates a struct you get default constructors, destructors etc...
Wrong numbers are never busy
[ Parent ]
More than that (none / 0) (#44)
by eMotion on Tue Apr 03, 2001 at 08:29:12 PM EST

Just a quick read of "The C++ Programming Language" will reveal more than that. Stroustroup devotes a section at the end to "technicalities" where valid C is not valid C++. Here are a few:

type specifier defaulting to int:

a = 7; /* means int a = 7; in C, not valid C++ */

enum boolean {true, false};

boolean b = 1; /* OK in C, not valid C++ because ints can't be assigned to enums */

[ Parent ]
Code reuse and C++ | 44 comments (8 topical, 36 editorial, 0 hidden)
Display: Sort:


All trademarks and copyrights on this page are owned by their respective companies. The Rest 2000 - Present Kuro5hin.org Inc.
See our legalese page for copyright policies. Please also read our Privacy Policy.
Kuro5hin.org is powered by Free Software, including Apache, Perl, and Linux, The Scoop Engine that runs this site is freely available, under the terms of the GPL.
Need some help? Email help@kuro5hin.org.
My heart's the long stairs.

Powered by Scoop create account | help/FAQ | mission | links | search | IRC | YOU choose the stories!