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?
-
Someone told me that some intel compilers wont work with static member functions: http://mail.gnome.org/archives/gtk-list/2011-March/msg00085.html – ntd Jan 19 '13 at 11:15
4 Answers
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.

- 1
- 1

- 333,147
- 50
- 533
- 760
-
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 Jan 14 '10 at 22:36
-
@peterchen: the problems can be portably solved using `extern "C"`, or is there something I'm missing? – Michael Burr Jan 14 '10 at 22:38
-
-
@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 Jan 14 '10 at 23:07
-
-
@Roger: I think I misunderstood what you were asking - I added quotes from the standard that I think actually address your question. – Michael Burr Jan 14 '10 at 23:24
-
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. – Jan 14 '10 at 23:40
-
@Roger: I found a normative part of the standard and replaced the non-normative note with it. – Michael Burr Jan 15 '10 at 00:06
-
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 Jan 15 '10 at 00:25
-
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. – Jan 15 '10 at 01:52
-
-
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 Jan 15 '10 at 02:05
-
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 Jan 15 '10 at 02:15
-
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. – Jan 15 '10 at 02:24
-
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. – Jan 15 '10 at 02:25
-
@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 Jan 15 '10 at 04:39
-
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. – Jan 15 '10 at 05:42
-
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". – Jan 15 '10 at 05:43
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); }
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.
-
1I 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 Jan 15 '10 at 01:05
-
4I 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 Jan 15 '10 at 04:38
-
1jalf: 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? – Jan 15 '10 at 05:49
-
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. :) – Jan 15 '10 at 05:54
-
1@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 Jan 15 '10 at 07:03
-
@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 Jan 15 '10 at 07:09
-
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 Jan 15 '10 at 07:15
-
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. – Jan 15 '10 at 07:17
-
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. – Jan 15 '10 at 07:21
-
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. :) – Jan 15 '10 at 07:23
-
(I misspoke, it can be implemented as a non-member, but not with C linkage as 14/4 specifically forbids that.) – Jan 15 '10 at 07:44
-
@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 Jan 15 '10 at 19:09
-
4What 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 Jan 15 '10 at 19:20
-
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. – Jan 15 '10 at 20:15
-
-
@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 Jan 16 '10 at 20:37
ABI isn't covered by either the C or C++ standards, even though C++ does give you "language linkage" through extern "C"
. Therefore, ABI is fundamentally compiler/platform specific. Both standards leave many, many things up to the implementation, and this is one of them.
Consequently, writing 100% portable code—or switching compilers—is hard to impossible, but allows vendors and users considerable flexibility in their specific products. This flexibility allows more space and time efficient programs, in ways that don't have to be anticipated in advance by the standards committees.
As I understand it, ISO's rules do not allow a standard more often than once every 10 years (but there can be various publications, such as TC1 and TR1 for C++). Plus there is the idea (I'm not sure if this comes from ISO, is carried over from the C committee, or even from elsewhere) to "distill"/standardize existing practice rather than going off into left field, and there are many existing practices, some of which conflict.
-
1Well, 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. – Jan 14 '10 at 22:38
-
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 Jan 14 '10 at 22:41
-
Neil: Exactly. The best you can do is say "make sure you do what your compiler requires". – Jan 14 '10 at 22:43
-
extern C is fine, but Martin seems to want to insist on making them non-member functions - or did I misread? – Jan 14 '10 at 22:45
-
You can declare a static member function extern "C"? Didn't know that. – Emile Cormier Jan 14 '10 at 22:48
-
1Emile: You cannot, only in namespace scope, which seems to be why he's insisting on not using static members. – Jan 14 '10 at 22:53
-
-
@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 Jan 14 '10 at 23:00
-
@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 Jan 14 '10 at 23:02
-
@Michael I don't really want to get into an ABI discussion (my brain is obviously near shutdown), so - goodnight! – Jan 14 '10 at 23:07
-
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. – Jan 14 '10 at 23:11
-
1Yes - 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 Jan 14 '10 at 23:40
-
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. – Jan 14 '10 at 23:49
-
@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 Jan 15 '10 at 00:30
-
@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 Jan 15 '10 at 07:19
-
@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 Jan 15 '10 at 07:24
-
As said, you cannot specify language linkage on member functions. (Re: second comment right before this.) – Jan 15 '10 at 07:24
-
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). – Jan 15 '10 at 07:31
-
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 Jan 15 '10 at 08:42
-
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). – Jan 15 '10 at 09:36
-
And indeed, comeau diagnoses that as expected in strict mode, but allows it in non-strict. (http://comeaucomputing.com/tryitout/) – Jan 15 '10 at 09:39