Last active
March 11, 2016 04:58
-
-
Save mikeando/517b89939030337286f0 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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