Any way to do this in C?

S

Snis Pilbor

Howdy,

Forgive me for not looking this up in the manuals or just trying
it outright, as I don't have access to a shell right now. Anyway, I
thought of this while juggling earlier: it would be neat if there were
a way to do a "sizeof" on the total set of declarations at the top of a
given function. For example:

#include <stdlib.h>
#include <stdio.h>

int main( int argc, char **argv )
{
char a;
char b;
int x,y,z;

printf( "The 'main' function declares %d bytes of data.\n\r", sizeof(
*main ) );
return 0;
}

Why would this be useful, you ask? Think nested recursion. Often,
programmers will clumsily use a semi-magical number to limit the max
depth before a recursion will be forcefully halted with an error.
Compare the following (you'll forgive the "code fragment" nature here,
since I'm using made-up syntax in the 2nd version anyway, there's
little point being pedantic elsewhere) (and yes, I know these use more
memory than needed but I'm doing that on purpose to illustrate my
point):

int my_recursive_fnc( int n )
{
static int depth;
int x,y,z,answer;

if ( ++depth > MY_MAGIC_NUMBER )
{
printf( "Fatal: recursion nest too deep\n\r" );
exit(0);
}

x = do_some_stuff( &n, &y, &z );
answer = my_recursive_fnc( n + x + y + z );
depth--;
return answer;
}

versus:

int my_recursive_fnc( int n )
{
extern int recursion_bytes;
int x,y,z,answer;

recursion_bytes += sizeof( *my_recursive_fnc );

if ( recursion_bytes > sysdata.max_space_for_recursion )
{
printf( "Fatal: recursion exceeded user-defined RAM limit\n\r" );
exit(0);
}

x = do_some_stuff( &n, &y, &z );
answer = my_recursive_fnc( n + x + y + z );
recursion_bytes -= sizeof( *my_recursive_fnc );
return answer;
}

In the 2nd version, instead of a magical hardcoded nest limit, we have
a realtime user-defined RAM limit which gives more leeway to more
efficient recursive functions and can nicely track recursion memory
throughout interwoven recursive functions that call eachother. This
could be done by manually adding the sizeof's of all the declared
variables as well as the passed-in arguments, but that would be a
maintainance and readability nightmare, and might even skip over
padding.
 
Z

Zara

Howdy,

Forgive me for not looking this up in the manuals or just trying
it outright, as I don't have access to a shell right now. Anyway, I
thought of this while juggling earlier: it would be neat if there were
a way to do a "sizeof" on the total set of declarations at the top of a
given function. For example:
<..>

You may group all of the local variables within a single struct, and
then evaluate sizeof the struct

Zara
 
S

Snis Pilbor

Zara said:
<..>

You may group all of the local variables within a single struct, and
then evaluate sizeof the struct

Zara

Oh hey, you are right Zara, that's a good point. =) Thanks for
bringing it up. But I see some problems with it. The biggest one is
that it doesn't include the memory used by the passed-in arguments: if
my function is "char some_function( char x )", it's going to use at
least 1 byte even if it declares no other memory at all. Though I
guess that's still a big improvement, since one tends to change
passed-in parameters much less frequently than variable declarations,
so one could manually add their sizeof's without quite as big a
maintainance nightmare. Well, I suppose you could pass in a struct as
well, but now we're getting into some really bad coding style territory
;)

S.P.
 
E

Eric Sosman

Snis said:
Oh hey, you are right Zara, that's a good point. =) Thanks for
bringing it up. But I see some problems with it. The biggest one is
that it doesn't include the memory used by the passed-in arguments: if
my function is "char some_function( char x )", it's going to use at
least 1 byte even if it declares no other memory at all. Though I
guess that's still a big improvement, since one tends to change
passed-in parameters much less frequently than variable declarations,
so one could manually add their sizeof's without quite as big a
maintainance nightmare. Well, I suppose you could pass in a struct as
well, but now we're getting into some really bad coding style territory

We're also getting into territory where compilers as a rule
feel particularly free to apply the "as if" rule. That one byte
you mention might well be in a register and take no stack space
at all. Even an `auto' variable inside a function may spend its
entire lifetime in a register and never be stored in memory.

On the other hand, even register-resident quantities may wind
up occupying stack space during calls to deeper functions or during
interrupt servicing. Some machines will save and restore registers
on every call, some will do a "window turn" and use the stack only
when the call nesting exceeds the window capacity, some will process
interrupts not by stacking anything but by switching to an alternate
register bank, ...

The result of all this variety is that there is no practical
way for a C programmer to get an accurate estimate of his program's
stack use except with reference to a particular implementation; the
next implementation (or the same one under different circumstances)
may use an entirely different amount of stack space. This is too
bad, because it leaves the programmer defenseless against stack
overflow: stack overflow is a characteristic of the implementations,
not of the language. There simply isn't a C-based way of telling
whether a call to foo() might blow the stack, nor a C-based way of
detecting or recovering from the situation should it occur.

The best I can offer is the usual "be sensible and hope for the
best" advice: Avoid creating large variables (usually arrays or big
structs) in `auto' storage, especially in recursive settings. Don't
fret about a few tens of individual variables; just declare them in
a natural manner and give the compiler maximum freedom to make use of
registers and so on. (In particular, sticking a bunch of scalars in
an artificial struct stands a good chance of increasing the amount of
stack space you'll use.) In recursive situations, do the pencil-and-
paper analysis to calculate the maximum (not average!) recursion depth
to get an idea of how parsimonious you need to be. And then hope.
 
A

Ancient_Hacker

The usual gang of cautious Cathys will tell you this is
implementation-dependent, non-portable, causes warts, etc. Here are
two ways to do it, perhaps a bit loopy, but will work on most
platforms. Don't expect it to work on the Rumanian Troika-13, with
it's trinary accumulator and gray-code stack register. This code is
really really useful if you have a lot of threads or signal handlers
and want to monitor and minimize their stack usage.

(1) Log the change in address of the local variables during one
recursion: Something roughly as so:

#define GetAddr -8888
#define GetInfo -9999


int WeRecurse( int x ){ int foo[1000], etc, etc, etc.......; char *
Here; char * Deeper;
unsigned long int Used;

if( x == GetAddr ) // flag to signal we're supposed to address of foo
return (int) &foo;

if( x == GetInfo ) { // flag to signal we're supposed to return size
of activation record
Here = (char *) &foo;
Deeper = WeRecurse( -888 );
Used = abs( Deeper - Here ); .
return( Used );
}

// normal code goes here
}

int main( void ) { int OneActivationSize;

OneActivationSize = WeRecurse( GetInfo );

}

---------------------------------------------------------------------------
(2) A bit dirtier, but does work on many platforms, caveat user:

#define Max 100000 // maybe about twice larger than the expected
activation record size

int PrepareAndSniffStack( char Cmd ) { char TheStack[ Max]; int Depth;
switch( Cmd ) {
case 'i': for( Depth = Max; TheStack[ Depth-- ] = '#'; Depth >= 0 )
;return 0;
case 'q': for( Depth = Max; TheStack[ i ] == '#'; Depth-- )
;return Depth;
}

}

int main( void ) {

PrepareAndSniffStack( 'i' ) ; /// Initialize the stack

TheRoutineToGetInfoOn(); // call the routine you want to measure

Used = PrepareAndSniffStack( 'q' ); /// query the stack usage

Notes:
(a) The info returned is going to include any stack space used by
signals, system calls, and even hardware interrupts (mainly on older
OS's).

(b)On the (very few) machines where the stack grows upward, reverse the
direction of the sniffing loop.

(c) Unlike (1), this one also notices any temporary stack space
allocated by the compiler to call functions that return arrays or
structs.
 
W

Walter Roberson

[in order to be able to dynamically adjust recursion limits]
Oh hey, you are right Zara, that's a good point. =) Thanks for
bringing it up. But I see some problems with it. The biggest one is
that it doesn't include the memory used by the passed-in arguments: if
my function is "char some_function( char x )", it's going to use at
least 1 byte even if it declares no other memory at all.

As others have pointed out, the size of arguments is implementation
dependant. But that's not a big problem.

As you might be aware, every recursive routine can be rewritten as
an iterative routine, memory permitting.

For example, you can establish a few variables to keep track of
where you are, and then for each new recusion level, you could malloc
a struct containing all the frame variables you might need,
chain the previous frame off of the end of that, set the structure
members that act as inputs, and loop back to near the beginning.
When your code would have returned from a recursion, then figure out
the "return value" (which might not be a simple numeric value), store it
locally for a moment, grab the address of the frame-variables
underneath, destroy the top frame, and continue onwards using the
return value and the newly-exposed frame variables. If there is no
pushed frame left, do your final cleanup and return the return value.
 

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

Forum statistics

Threads
473,774
Messages
2,569,598
Members
45,151
Latest member
JaclynMarl
Top