views:

470

answers:

4

In C++, is it safe/portable to use static member function pointer for C API callbacks? Is the ABI of a static member function the same as a C function?

+4  A: 

For all the Windows C++ compilers that I'm aware of, the answer is yes, but nothing in the language standard guarantees this. I wouldn't let that stop you however, it's a very common way of implementing callbacks using C++ - you may find you need to declare the static functions as WINAPI however. This is taken from an old threading library of my own:

class Thread {
   ...
   static DWORD WINAPI ThreadFunction( void * args );
};

where this is the callback use by te Windows threading API.

anon
I have lost the link but GCC (in the latest release) also uses the same calling convention for static methods and C functions so gcc users are also safe.
Martin York
I don't really see the point in this though. I mean, if the function is a static member *anyway*, why not just play it safe and do it portably by going through a non-member function in the first place?
jalf
jalf: Because you still have only the illusion of safety, as you're at the whim of the implementation. Apparently it's a *tiny* bit more portable (I still haven't heard back on which compilers this affects), but, as I'm sure you know, this is not the same as being guaranteed by the standard. Why take great pains to work around issues that *aren't even present* in your implementation and which you don't expect to affect you in the next 5 years?
Roger Pate
That said, I'm interested because I *want* to understand the dark corners of the language instead of just the implementations I use, which I presume is the same or similar reason for everyone else analyzing this in depth. :)
Roger Pate
@Roger: "Great Pains!" If you calling moving a static method declaration two lines up in a header file and prefixing it with extern "C" a great pain then coding must be a migraine.
Martin York
@Roger: I also disagree with the "Illusion of safety". The extern "C" specified 7.5.3 __"Every implementation shall provide for linkage to functions written in the C programming language"__
Martin York
The way I see it. There is no additional cost (it is a style change to be correct). And the results are guaranteed. Callback functions are a throwback to C-libraries where type safety could not be guaranteed and as a result is uncommon in modern C++ code only appearing when we need to interface with C libraries. Since we are interfacing with C-libraries are usage should be symmetrical thus a C-functions should be used.
Martin York
Martin: It's *never* that simple in real code. I use a nice template trick to generate these for me, but it requires using static member functions and cannot be made to work with non-members. Here it is, stripped to the bare essentials: http://codepad.org/V7KYKd0v.
Roger Pate
Martin: It is only the illusion of safety because unless you know *exactly* how the C library was compiled, you *don't know* that your compiler is using the correct ABI. But if you do know *your* compiler and that compiler, then you can know whether it will work or not. When trying to write 100% portable code, you can't know *either* compiler.
Roger Pate
Martin: Perhaps you missed it on the question which spawned this question: in what compilers does this break? (I really want to know before I have to find out the hard way. :)
Roger Pate
(I misspoke, it can be implemented as a non-member, but not with C linkage as 14/4 specifically forbids that.)
Roger Pate
@Roger: I disagree with you have to know how the C library was compiled to know the ABI. The ABI is not defined by the compiler but the standard library that it uses. The compler must conform to that (as the standard library is binary not source) thus if you know the standard library that is being used you know what the C-ABI is and thus both the C and C++ compiler will use the same C-ABI.
Martin York
What compiler does it break. At my last Job I compiled ACE/TAO and some company code on 25 compiler/OS/Hardware configuration. Built in 8 (debug/release - single-multi thread - shared/static lib) flavors on each configuration. Of those 200 versions I found the problem in 3 (I had a reproducable problem in three I could debug) it might have affected others, but it took me forever to identify the problem and fix. The exact compilers/version escapes me (it was 5 years ago) but I think it was an older Sun compiler and an older AIX compiler,I could be wrong. they were older versions of the compiler
Martin York
Shifting it from the compiler to the stdlib doesn't change much as far as that goes (and is partially covered anyway by saying "platform")---you can still have several different implementations of the stdlib and you're still depending on something not specified in either the standard or in your code. Or in other words, the external library (glut in the original example) has to be compiled for that platform and you have to make sure it's compiled correctly so that you can call it. Thanks for the info and for bringing this up, I learned quite a bit about this dark corner.
Roger Pate
@Martin: that's quite a war story!
Michael Burr
@Roger: So you are saying that assuming the std lib is built correctly and that both the C and C++ compilers conform to the standard and use the same ABI as the std lib and their are no bugs then everything should work. I think that is an assumption you have to make. These are the basic meta components on-top of which we build. If any of the above fail then nothing would work.
Martin York
+12  A: 

It is not safe per the C++ standard. As stated in this SO posting:

A C callback function implemented in C++ must be extern "C". It may seem to work as a static function in a class because class-static functions often use the same calling convention as a C function. However, doing that is a bug waiting to happen (see comments below), so please don't - go through an extern "C" wrapper instead.

And according to comments made by Martin York in that answer there are real-world problems trying to do so on some platforms.

Make your C ABI callbacks extern "C".


Edit: Adding some supporting quotes from the standard (emphasis mine):

3.5 "Program and linkage":

After all adjustments of types (during which typedefs (7.1.3) are replaced by their definitions), the types specified by all declarations referring to a given object or function shall be identical, except that declarations for an array object can specify array types that differ by the presence or absence of a major array bound (8.3.4). A violation of this rule on type identity does not require a diagnostic. [3.5/10]

[Note: linkage to non-C++ declarations can be achieved using a linkage-specification (7.5). ] [3.5/11]

And

7.5 "Linkage specifications":

... Two function types with different language linkages are distinct types even if they are otherwise identical. [7.5/1]

So if the code making the callback is using C language bindings for the callback, then the callback target (in the C++ program) must as well.

Michael Burr
Thanks for the link - still IMO it should be noted that in practice all compilers (all I've worked with...) seem to provide non-portable solutions - like a calling convention declaration - to solve the problems.
peterchen
@peterchen: the problems can be portably solved using `extern "C"`, or is there something I'm missing?
Michael Burr
"Per the C++ standard"? What part of the standard says this?
Roger Pate
@Roger: in discussing 'language linkage', 7.5/3 says "Every implementation shall provide for linkage to functions written in the C programming language" meaning `extern "C"` must be supported.
Michael Burr
What you quoted does not say that using static methods is not safe.
Roger Pate
@Roger: I think I misunderstood what you were asking - I added quotes from the standard that I think actually address your question.
Michael Burr
I always find it disheartening when the best support is a note (which are non-normative and not binding). But that helps, thanks. I'm glad I don't work at a place that makes it extremely embarrassing (as in the comments on your linked post) to get confused over an issue the language makes so vague.
Roger Pate
@Roger: I found a normative part of the standard and replaced the non-normative note with it.
Michael Burr
Thanks, Michael. I'll need to parse your answer and quotes carefully with rested eyes before I accept it, but it looks good so far.
Emile Cormier
Michael: Are you trying to say 3.5/9 in the C++ standard covers the code making the callback, which is written in an entirely different language? Additionally, that only talks about declarations, and I'm not sure how it would even apply to passing parameters to a function.
Roger Pate
3.5/10, actually, and I've fixed the references in your answer.
Roger Pate
The C++ standard only covers the C++ program, but clearly the declaration must match the calling convention used by the caller (even if the caller isn't written in C or C++). If the caller is using a C calling convention (what the standard calls a "language linkage"), then it must be declared `extern "C"` in the C++ program. The platform would define what the C linkage conventions are. If something else is used by the caller (which isn't what I understood this question to be asking), then `extern "C"` wouldn't be appropriate and you're getting into a non-portable implementation defined area.
Michael Burr
Note: I recognize that static members will usually work (and always I think on Windows). In the post I linked to, I first suggested as much in my answer, but then Martin indicated that he'd run into actual implementations where it didn't work, so I modified my answer to suggest avoiding using them as callbacks (it changed from a language-lawyer issue to the real-world). I wouldn't think less of anyone using the technique - I've certainly done so many times in the past. But, I'd hope that if it came up in code review, the take away would be to use an `extern "C"` wrapper. Even on Windows.
Michael Burr
Michael: Understood, and that's not the issue here; I don't mean to pick on your answer, but you're putting effort into improving it, and I would be much happier knowing that it really is required by the standard and *where the standard requires it*. Otherwise, I think you have to fall back on "completely outside the scope of the standard and up to the implementation", as I answered this question.
Roger Pate
The best I can find is that you should not be able to convert the function pointer types (and that it should be a compile time error), but even that is vague---based off 7.5/1 and not seeing anything that allows the conversion. Also, 3.5 looks increasingly not related.
Roger Pate
@Roger: to me, a reading of 3.5 and 7.5 seem to make this pretty clear (in fact, I think this is a major reason for the existence of 7.5). But I'd be the first to say that I'm no authority on the language or the standard and it's possible I'm reading more into those passages than what's there (though at this point in time I don't think I am). But, using an `extern "C"` wrapper seems unquestionably safe for C callbacks while there's some uncertainty about using static members. Maybe the question should be turned a bit - does the standard explicitly permit static members for C callbacks?
Michael Burr
They both use "linkage", but 3.5 is about internal/external/no linkage (which is important for the definition of the ODR), and 7.5 is about making "language linkage" work within the framework of 3.5 (reading 7.5/2's first sentence in particular). "Unquestionably safe", without specifying implementation, is out the window by 7.5/1's "Some of the properties .. are specific to each implementation and not described here. For example, .. a particular calling convention, etc." Unfortunately, that is only a note, and it seems the normative part of the standard only specifies this by omission.
Roger Pate
Also from 7.5/1: "All function types, function names, and variable names have a language linkage." That's in addition to external/internal/"no linkage", and highlights, at least for me, the orthogonality of the two ways the standard uses "linkage".
Roger Pate
+3  A: 
Roger Pate
Well, I disagree with Martin, and years of Windows programming experience would seem to back this up. And as you observe, there is no standard ABI, so using a C function has no guarantees either.
anon
if you're only using Windows compilers, you'll be fine... until you port your code to Android or Linux or Mac or whatever, then you might find your 'working' code doesn't work. Safest to use extern C - it isn't exactly much extra work.
gbjbaanb
Neil: Exactly. The best you can do is say "make sure you do what your compiler requires".
Roger Pate
extern C is fine, but Martin seems to want to insist on making them non-member functions - or did I misread?
anon
You can declare a static member function extern "C"? Didn't know that.
Emile Cormier
Emile: You cannot, only in namespace scope, which seems to be why he's insisting on not using static members.
Roger Pate
Well, I thought you could, but it seems I may be halucinating :-)
anon
In fact obviously you cannot - the name has to be mangled.
anon
@gbjbaanb: if you want to port Windows code that uses a callback to work under Linux, Android, MacOS, etc., the callback being a static member function vs. a global extern "C" function is going to be the least of your problems (unless, of course, you're starting with something like Qt or wxWidgets, in which case you probably won't be writing such callbacks to start with).
Jerry Coffin
@Neil - assuming the callback API is using a C ABI, then `extern "C"` is completely portable and specified by the standard. Of course, if the callback isn't using a C ABI, you're in non-portable land for sure, so do whatever works.
Michael Burr
@Michael I don't really want to get into an ABI discussion (my brain is obviously near shutdown), so - goodnight!
anon
Michael: There is no C ABI, there are ABIs used for C on particular platforms. I think you're mixing up the C calling convention (which is not what `extern "C"` specifies anyway), and Martin is talking about something that is *not* the C calling convention.
Roger Pate
Yes - an ABI is platform specific. A C++ program with an `extern "C"` function declaration in it compiled for a particular platform will expect the function to be using the C ABI for that platform.
Michael Burr
Neil: No, it's not obvious, and there was even a DR about it: http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_closed.html#168.
Roger Pate
@Neil: Note to self... to get Neil B to bow out of a discussion/argument, bring up ABIs. If a little extra push is necessary, talk about typedef-ing structs. :P
Michael Burr
@Niel: I would have no problem with static methods if we could use extern "C" on them. But I can not seem to make that work. Maybe I am not getting the syntax correct.
Martin York
@Roger: The C ABI may not be defined explicitly in the standard (because it is usally linked to the underlying hardware that supports it) but it must be defined by the implementation, which is limited by the standard library it uses (is that a chicken and egg [does the compiler define the ABI and build the standard lib or the standard lib define an ABI thus the compiler must follow the convention?]). Anyway the C++ standard guarantees that extern "C" provides linkage to functions written in C. This is not just calling convention but also ABI.
Martin York
As said, you cannot specify language linkage on member functions. (Re: second comment right before this.)
Roger Pate
Martin: The exact issue you've brought up is compilers that have their own highly-optimized ABIs and pass parameters in registers. Once you accept that an external library can be compiled to use a different ABI than the one for the current TU, then **anything you do depends on implementation specifics**, *because* the standard doesn't explicitly define ABIs. The issue is not providing linkage to functions written in C, but providing linkage from C functions to functions written in C++ (the static member functions).
Roger Pate
Roger: Here's my take for what it's worth - Since a linkage specification can only happen at namespace scope, member functions can't have a language linkage specified so they get the default C++ linkage. If the static member function happens to work when called by a C function, then that's just luck (or maybe a language extension if the vendor makes the promise that it will). Similar to if `a = a++ + ++a` does what you expect (whatever that might be - it gives me `5` four out of five times).
Michael Burr
Yes, I've learned it's wholly an implementation detail as far as the standard is concerned, as a result of this discussion (and I'm glad to have learned that), so I fully agree with you on that part. What I'm looking at now is what requirements the standard puts on `extern "C" typedef void (*Callback)(); extern "C" void reg(Callback); void f() {} int main() { reg(f); }`, which, as far as I can tell, converts function types in a way not allowed and should result in an ill-formed program according to 13.3/4 as there is no implicit conversion sequence (13.3.2/3).
Roger Pate
And indeed, comeau diagnoses that as expected in strict mode, but allows it in non-strict. (http://comeaucomputing.com/tryitout/)
Roger Pate
+3  A: 

After searching and several breaks while attacking other problems, I found an answer which is clear and succinct (for standardese, anyway):

Calling a function through an expression whose function type has a language linkage that is different from the language linkage of the function type of the called function's definition is undefined. [5.2.2/1]

I still maintain that it is problematic at a fundamental level to use text from the C++ standard to define the behavior of a C library compiled with a C compiler, and exactly how that interlanguage interoperability works is very implementation-specific; however, this is the closest I think either standard can (currently) hope to define such interaction.

In particular, this is undefined behavior (and isn't using a C library so that issue doesn't arise):

void call(void (*pf)()) { pf(); } // pf() is the UB
extern "C" void f();
int main() { call(f); }
// though I'm unsure if a diagnostic is required for call(f)

Comeau does give a diagnostic at call(f) (though it can do that even if the diagnostic isn't required).

This isn't undefined behavior, and shows how to include language linkage in a function pointer type (which is through a typedef):

extern "C" typedef void F();
void call(F* pf) { pf(); }
extern "C" void f();
int main() { call(f); }

Or could be written:

extern "C" {
typedef void F();
void f();
}
void call(F* pf) { pf(); }
int main() { call(f); }
Roger Pate