Two macros for resource management

S

Stefan Ram

Some years ago, I invented two preprocessor macros for
resource management, which I publish here in the form of this
exercise. I intend to publish a solution as of 2004-08-01,
unless it already have been found. Until then, readers are
encouraged to publish their solutions.

In a function, three (for example) resources are to be
allocated. Then, some work is to be done using these
resources, and finally the resources are to be released.

Moreover: should an attempt to allocate a resource fail, the
work cannot be done and exactly those resources that already
have been allocated successfully so far should be released.
Below, the operation "buffcopy" is just as an example of such a
work, which is using the resources, and not defined here.

The macros have been designed so that release actions appear
next to their corresponding obtain actions and a sequence of
multiple allocations does not require nesting to enhance
readability and maintainability.

#include <stdlib.h> /* malloc, free, EXIT_FAILURE */
#include <stdio.h> /* FILE, fopen, fclose, fread, fwrite */
#include "example.h" /* buffcopy */

/* define TRY and PUSH here. (this is the exercise.) */

int main( void )
{ int result = EXIT_FAILURE;
FILE * source;
FILE * target;
char * buffer;
TRY( source = fopen( "source", "r" ))
PUSH( fclose( source ))
TRY( target = fopen( "target", "w" ))
PUSH( fclose( target ))
TRY( buffer = malloc( 1024 ))
PUSH( free( buffer ))
result = buffcopy( target, source, buffer );
return result; }
 
R

Richard Bos

TRY( source = fopen( "source", "r" ))
PUSH( fclose( source ))

This is not a good idea at all. Every single maintenance programmer who
reviews this code will put semicolons after these "statements", thus
breaking your macros.

Richard
 
P

Peter Nilsson

Stefan Ram said:
Some years ago, I invented two preprocessor macros for
resource management, which I publish here in the form of this
exercise. I intend to publish a solution as of 2004-08-01,
unless it already have been found. Until then, readers are
encouraged to publish their solutions.

In a function, three (for example) resources are to be
allocated. Then, some work is to be done using these
resources, and finally the resources are to be released.

Moreover: should an attempt to allocate a resource fail, the
work cannot be done and exactly those resources that already
have been allocated successfully so far should be released.
Below, the operation "buffcopy" is just as an example of such a
work, which is using the resources, and not defined here.

The macros have been designed so that release actions appear
next to their corresponding obtain actions and a sequence of
multiple allocations does not require nesting to enhance
readability and maintainability.

#include <stdlib.h> /* malloc, free, EXIT_FAILURE */
#include <stdio.h> /* FILE, fopen, fclose, fread, fwrite */
#include "example.h" /* buffcopy */

/* define TRY and PUSH here. (this is the exercise.) */

int stack[3], *sptr = &stack[sizeof stack / sizeof *stack];

#define TRY(alloc) \
for (*--sptr = 0; \
*sptr != 0 \
|| (*sptr == 0 && (alloc) != 0) \
|| (*sptr == 0 && (sptr++, 0)) \
; )

#define PUSH(dealloc) \
if (++*sptr == 2) { (dealloc); sptr++; break; } else
int main( void )
{ int result = EXIT_FAILURE;
FILE * source;
FILE * target;
char * buffer;
TRY( source = fopen( "source", "r" ))
PUSH( fclose( source ))
TRY( target = fopen( "target", "w" ))
PUSH( fclose( target ))
TRY( buffer = malloc( 1024 ))
PUSH( free( buffer ))
result = buffcopy( target, source, buffer );

If there's more than one statement to be performed, it needs to be enclosed in a block.
return result; }

I'd prefer a solution where TRY and PUSH statements are ; terminated. You also lose
control over fclose return values. Regaining control using these macros would be more
trouble than it's worth. Even if it wasn't, I still wouldn't use this. ;-)
 
S

Stefan Ram

This is not a good idea at all. Every single maintenance programmer who
reviews this code will put semicolons after these "statements", thus
breaking your macros.

Thanks for the two answer postings I have read so far! I will
reply to the contents of the answers at august, 1; when I will
post my solution.
 
N

Nick Austin

#include <stdlib.h> /* malloc, free, EXIT_FAILURE */
#include <stdio.h> /* FILE, fopen, fclose, fread, fwrite */
#include "example.h" /* buffcopy */

/* define TRY and PUSH here. (this is the exercise.) */

int main( void )
{ int result = EXIT_FAILURE;
FILE * source;
FILE * target;
char * buffer;
TRY( source = fopen( "source", "r" ))
PUSH( fclose( source ))
TRY( target = fopen( "target", "w" ))
PUSH( fclose( target ))
TRY( buffer = malloc( 1024 ))
PUSH( free( buffer ))
result = buffcopy( target, source, buffer );
return result; }

Forget this unreadable and unmaintainable crap.

Also it would be nice to test the return code from fclose().

If you must use macros do it something like this:


#define CLOSE_FILE( f ) \
if ( f ) \
{ \
if ( fclose( f ) ) \
result = EXIT_FAILURE; \
}

int main( void )
{
int result = EXIT_FAILURE;
FILE * source = NULL;
FILE * target = NULL;
char * buffer = NULL;

source = fopen( "source", "r" );
target = fopen( "target", "w" );
buffer = malloc( 1024 );

if ( source && target && buffer )
result = buffcopy( target, source, buffer );

CLOSE_FILE( source );
CLOSE_FILE( target );
free( buffer );

return result;
}

Nick.
 
J

jacob navia

Nick Austin said:
On 14 Jul 2004 11:28:15 GMT, (e-mail address removed)-berlin.de (Stefan Ram)
wrote:

If you must use macros do it something like this:


#define CLOSE_FILE( f ) \
if ( f ) \
{ \
if ( fclose( f ) ) \
result = EXIT_FAILURE; \
}

int main( void )
{
int result = EXIT_FAILURE;
FILE * source = NULL;
FILE * target = NULL;
char * buffer = NULL;

source = fopen( "source", "r" );
target = fopen( "target", "w" );
buffer = malloc( 1024 );

if ( source && target && buffer )
result = buffcopy( target, source, buffer );

CLOSE_FILE( source );
CLOSE_FILE( target );
free( buffer );

return result;
}

I would just change
free(buffer)
to
if (buffer) free(buffer);
 
S

Stefan Ram

TRY( source = fopen( "source", "r" ))
PUSH( fclose( source ))
TRY( target = fopen( "target", "w" ))
PUSH( fclose( target ))
TRY( buffer = malloc( 1024 ))
PUSH( free( buffer ))
result = buffcopy( target, source, buffer );
return result; }

In the above posting, I raised the question how to define
"TRY" and "PUSH" and I now will reply to some
Followup-postings and give my own solution.

Richard said:
This is not a good idea at all. Every single maintenance
programmer who reviews this code will put semicolons after
these "statements", thus breaking your macros.

This should be taken into account, when deciding whether to
use this approach.

These two macros are of limited use for production code.
Their main goal is to show a way to look a C code and resource
management that might not be obvious.

Peter said:
int stack[3], *sptr = &stack[sizeof stack / sizeof *stack];

#define TRY(alloc) \
for (*--sptr = 0; \
*sptr != 0 \
|| (*sptr == 0 && (alloc) != 0) \
|| (*sptr == 0 && (sptr++, 0)) \
; )

#define PUSH(dealloc) \
if (++*sptr == 2) { (dealloc); sptr++; break; } else

This is indeed a working solution, which differs from mine,
although both use a for-loop.
If there's more than one statement to be performed, it needs
to be enclosed in a block.

Yes.
I'd prefer a solution where TRY and PUSH statements are ;
terminated. You also lose control over fclose return values.
Regaining control using these macros would be more trouble than
it's worth. Even if it wasn't, I still wouldn't use this. ;-)

The approach taken is indeed of limited flexibility.

I do not believe that I would use this approach often in
production code, but still like the idea to abstract from the
details of resource management.

**

Now, it will be shown how to arrive at my own solution. The
first program is doing what is required (except for possible
inspection of the results of "fclose"), but without those two
macros.

.-----------------------------------------------------------.
| Warning: When the following programs are executed, they |
| might overwrite (delete) an existing file named "target". |
'-----------------------------------------------------------'


/* < filename = [twomac0.c] > */

#include <stdlib.h> /* malloc, free, EXIT_FAILURE */
#include <stdio.h> /* FILE, fopen, fclose */
#include "example.h" /* buffcopy */

int main( void )
{ int result = EXIT_FAILURE;
FILE * source;
FILE * target;
char * buffer;
if( source = fopen( "source", "r" ))
{ if( target = fopen( "target", "w" ))
{ if( buffer = malloc( 1024 ))
{ result = buffcopy( target, source, buffer );
free( buffer ); }
fclose( target ); }
fclose( source ); }
return result; }

The code is rewritten to use a while-statement:

/* < filename = [twomac1.c] > */

#include <stdlib.h> /* malloc, free, EXIT_FAILURE */
#include <stdio.h> /* FILE, fopen, fclose */
#include "example.h" /* buffcopy */

int main( void )
{ int result = EXIT_FAILURE;
FILE * source;
FILE * target;
char * buffer;
int try = 1;
{ while( try &&( source = fopen( "source", "r" )))
{ while( try &&( target = fopen( "target", "w" )))
{ while( try &&( buffer = malloc( 1024 )))
{ result = buffcopy( target, source, buffer );
try = 0; free( buffer ); }
try = 0; fclose( target ); }
try = 0; fclose( source ); }}
return result; }

The variable "try" was used to have each loop be executed at
most once. Thus, it helps "emulating" the if-statements using
the while-statements. Why was the while-loop introduced above
at all? Because the code now can be rewritten to use
for-statements:

/* < filename = [twomac2.c] > */

#include <stdlib.h> /* malloc, free, EXIT_FAILURE */
#include <stdio.h> /* FILE, fopen, fclose */
#include "example.h" /* buffcopy */

int main( void )
{ int result = EXIT_FAILURE;
FILE * source;
FILE * target;
char * buffer;
for
( int try = 1;
try &&( source = fopen( "source", "r" ));
try = 0, fclose( source ))
for
( try = 1;
try &&( target = fopen( "target", "w" ));
try = 0, fclose( target ))
for
( try = 1;
try &&( buffer = malloc( 1024 ));
try = 0, free( buffer ))
result = buffcopy( target, source, buffer );
return result; }

Now, one can see how the "magic" works. If one does not like
macros, the for-statements can be used as they are given above
to be able to write close-statements near to their
corresponding open statement. Otherwise, they might be packed
into macros:

/* < filename = [twomac.c] > */

#include <stdlib.h> /* malloc, free, EXIT_FAILURE */
#include <stdio.h> /* FILE, fopen, fclose */
#include "example.h" /* buffcopy */

#define TRY(x) for(int try=1;(try&&(x));
#define PUSH(y) ((try=0),(y)))

int main( void )
{ int result = EXIT_FAILURE;
FILE * source;
FILE * target;
char * buffer;
TRY( source = fopen( "source", "r" ))
PUSH( fclose( source ))
TRY( target = fopen( "target", "w" ))
PUSH( fclose( target ))
TRY( buffer = malloc( 1024 ))
PUSH( free( buffer ))
result = buffcopy( target, source, buffer );
return result; }

The next programm features a debug-version of the code.
It can be executed as it is, because it does not contain
a reference to the undefined function "buffcopy".

The comparison in the function "mymalloc" might be modified to
make the function "mymalloc" return 0 (by changing the value
"2" to "1", "0" or "-1"). This might be used to test all
possible branches of control flow and to observe that the
function "myfree" only is called for those calls of the
function "mymalloc", which did not return 0; and that the
function "printf" only is called if all attempts did succeed.

/* < filename = [twomacd.c] > */

#include <stdlib.h> /* EXIT_SUCCESS, EXIT_FAILURE */
#include <stdio.h> /* fprintf, printf */

#define TRY(x) for( int try=1; (try&&(x));
#define PUSH(y) ((try=0),(y)))

void *
mymalloc
( size_t const s )
{
static int i = 0;
void * const result = i > 2 ? 0 : malloc( s );
fprintf( stderr, "%p = mymalloc( %zu );\n", result, s );
++i;
return result; }

void
myfree
( char * const p )
{
fprintf( stderr, "free( %p );\n", p );
free( p ); }

int
main
( void )
{
int result = EXIT_FAILURE;
char *a;
int *b;
double *c;

TRY( a = mymalloc( 2 * sizeof( *a )))
PUSH( myfree( a ))
TRY( b = mymalloc( 3 * sizeof( *b )))
PUSH( myfree(( char * )b ))
TRY( c = mymalloc( 4 * sizeof( *c )))
PUSH( myfree(( char * )c ))

{ printf( "%p %p %p\n",( void * )a, ( void * )b, ( void * )c );
result = EXIT_SUCCESS; }

return result; }
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,014
Latest member
BiancaFix3

Latest Threads

Top