Skip to content

Instantly share code, notes, and snippets.

@mikeando
Last active March 11, 2016 04:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mikeando/517b89939030337286f0 to your computer and use it in GitHub Desktop.
Save mikeando/517b89939030337286f0 to your computer and use it in GitHub Desktop.
#include <cerrno>
#include <iostream>
#include <typeinfo>
//Here's a simple function that does exactly what we might expect
void testx( int * errnox ) {
std::cout<<"in testx type of errno = "<<typeid(errnox).name() <<std::endl;
if(errnox) {
*errnox = 7;
std::cout<<"errnox is "<<errnox<<std::endl;
} else {
std::cout<<"errnox is not available"<<std::endl;
}
}
bool g_explode = false;
// But if we rename the argument to errno "bad things happen".
// It compiles without problem, but doesn't do what you might expect.
// The compiler would disallow most places you tried to use it... (but not all .. see the
// end for potential examples )
// If you manage to call it, it will crash (if g_explode=true)
void test( int* errno ) {
std::cout<<"in test type of errno = "<<typeid(errno).name() <<std::endl;
// we can use errno is may of the ways that we might expect, but they cause crashes
if(g_explode) {
if(errno) {
*errno = 7;
std::cout<<"errno is "<<errno<<std::endl;
} else {
std::cout<<"errno is not available"<<std::endl;
}
}
}
// Why? Because errno is a macro:
// # define errno (*__errno_location ())
// which means the compiler threats the definition of test as
void test_expanded( int**__errno_location() ) {
// i.e. the argument __errno_location is a zero-argument function
// that returns an int**
// typeid doesn't actually evaluate the expression, just looks at its types
// and so *__errno_location() has the type returned by dereferencing the
// int** return value of calling __errno_location() which is int* as expected
std::cout<<"in test_expanded type of errno = "<<typeid((*__errno_location ())).name()<<std::endl;
if(g_explode) {
// would actually calls the argument as a function
if(*__errno_location ()) {
**__errno_location () = 7;
std::cout<<"errno is "<<*__errno_location ()<<std::endl;
} else {
std::cout<<"errno is not available"<<std::endl;
}
}
}
int main() {
std::cout << "type of testx = "<< typeid(testx).name() << std::endl; // type of testx = FvPiE
testx(NULL); // in testx type of errno = Pi
std::cout << "type of test = "<< typeid(test).name() << std::endl; // type of test = FvPFPPivEE
test(NULL); // in test type of errno = Pi
g_explode = true;
test(NULL);
// Note the compiler will pick up most usages of `test` and flag them as bad.
// e.g. for test(1) icc gives
// error: argument of type "int" is incompatible with parameter of type "int **(*)()"
//
// However as you can see above NULL is a valid value for both int and int**(*)()
//
}
// So why does this suck...
//
// There's a few cases where the types of functions are ignored, and that is typically
// in cross language boundaries or dynamic loading of code. In these cases we pass an integer to the function
// as that is what it looks like it expects, and the code ends up trying to call that integer as a function.
//
// Note that if errno was a global (like it used to be before it became thread-safe) this would not
// happen, as we'd just end up hiding the global within the function.
//
// This bit me when bridging C to fortran code.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment