Tech News
← Back to articles

The Cost of a Closure in C

read original related products more articles

I had a vague idea that closures could have a variety of performance implications; I did not believe that so many of the chosen and potential designs for C and C++ extensions ones, however, were so… suboptimal.

But, before we get into how these things perform and what the cost of their designs are, we need to talk about what Closures are.

“Closures”?

Closures in this instance are programming language constructs that includes data alongside instructions that are not directly related to their input (arguments) and their results (return values). They can be seen as a “generalization” of the concept of a function or function call, in that a function call is a “subset” of closures (e.g., the set of closures that do not include this extra, spicy data that comes from places outside of arguments and returns). These generalized functions and generalized function objects hold the ability to do things like work with “instance” data that is not passed to it directly (i.e., variables surrouding the closure off the stack) and, usually, some way to carry around more data than is implied by their associated function signature.

Pretty much all recent and modern languages include something for Closures unless they are deliberately developing for a target audience or for a source code design that is too “low level” for such a concept (such as Stack programming languages, Bytecode languages, or ones that fashion themselves as assembly-like or close to it). However, we’re going to be focusing on and looking specifically at Closures in C and C++, since this is going to be about trying to work with and – eventually – standardize something for ISO C that works for everyone.

First, let’s show a typical problem that arises in C code to show why closure solutions have popped up all over the C ecosystem, then talk about it in the context of the various solutions.

The Closure Problem

The closure problem can be neatly described by as “how do I get extra data to use within this qsort call?”. For example, consider setting this variable, in_reverse , as part of a bit of command line shenanigans, to change how a sort happens:

#include #include #include static int in_reverse = 0 ; int compare ( const void * untyped_left , const void * untyped_right ) { const int * left = untyped_left ; const int * right = untyped_right ; return ( in_reverse ) ? * right - * left : * left - * right ; } int main ( int argc , char * argv []) { if ( argc > 1 ) { char * r_loc = strchr ( argv [ 1 ], 'r' ); if ( r_loc != NULL ) { ptrdiff_t r_from_start = ( r_loc - argv [ 1 ]); if ( r_from_start == 1 && argv [ 1 ][ 0 ] == '-' && strlen ( r_loc ) == 1 ) { in_reverse = 1 ; } } } int list [] = { 2 , 11 , 32 , 49 , 57 , 20 , 110 , 203 }; qsort ( list , ( sizeof ( list ) / sizeof ( * list )), sizeof ( * list ), compare ); return list [ 0 ]; }

This uses a static variable to have it persist between both the compare function calls that qsort makes and the main call which (potentially) changes its value to be 1 instead of 0 . Unfortunately, this isn’t always the best idea for more complex programs that don’t fit within a single snippet:

... continue reading