Getting the size of a C function

Jan 22, 2010 75 Replies

Ditto.

If the function is not linked to the RAM run address then it probably won't work.

Did this a year or so ago with Metrowerks and an HC12 but don't remember quite how. It wasn't terribly hard once one knows what is needed. The linker properly generated constant "variables" containing start and end addresses for me. Quite properly it would not generate the runtime code for moving the image from FLASH to RAM as this code was not intended to permanently reside in RAM.

You've also got to be careful in RAM-resident routine not to write any code that generates library calls. On a 16-bit CPU, doing long arithmetic will likely generate a library call, so be mindful of which integer types you use.

Grant Edwards grante Yow! FROZEN ENTREES may at be flung by members of visi.com opposing SWANSON SECTS ...

Constant values should, like everything else, be declared and defined in the place that makes most sense for the structure of the program. That may mean just locally within a module or function, or in a module's header file, in a program-global header, or occasionally declared in a header and defined in an implementation file (for better data hiding, though perhaps missed optimisation opportunities). So the only rule here is to put them in the right place for the program, not just because it shaves a quarter second off the re-compile time.

Rules in embedded development are never absolute - we have both said as much in this thread. But there are plenty of rules, written and unwritten, that are strong enough to state as though they always apply. If you feel you need to break them, you do so when you have clear and reasoned arguments why your software will be better with the rule broken.

I can't imagine a time when I would need to do that, or any problem it might solve. Sometimes you want symbols that are defined at the linker level to be exported to C - the start and end of a section, for example

- but I don't see any reason to pass constants around within the C program itself in this way. I suppose you might have an assembly module which exports constants that you then want to use in C, but it would be better to use a common #define that is available to both C code and the assembly code.

Can you give me an example in which this is actually a required way to handle such constants? As I said above, you could have "#define QUANTUM

47" in a header that is included by both the C code and assembly code as needed. (And if your assembler doesn't like that sort of syntax, get a better assembler. If that fails, between the C preprocessor and the assembler's macro capabilities, you should be able to concoct a common way of including the constants. And if that also fails, write a script that is called by your Makefile and generates the required headers and include files.)

I agree entirely that exposing the global constants is often the best way of using such values - I hate these silly little "accessor" functions people write because they think that global data (variable or constant) is somehow "bad", and it's better to write an inefficient and unclear global function instead. But your constants should be available to the compiler at compile time if at all possible - having them available only at link time wastes your compiler's strengths.

On devices for which I write my own CRT0 or other pre-C startup code, I write it in C. Typically there's a couple of lines of assembly to set the stack pointer and jump to the C startup function, but things like clearing .bss and copying .data are all done in C. If I wanted sections that may or may not be included, I'd use C - either with #if's, or by relying on the compiler to eliminate dead code. And some symbols, such as section start and end points, are passed from the linker into the C code - but only those that /must/ be passed in that way.

Apparently the quality controllers and testers of your particular compiler would argue otherwise!

It certainly isn't an easy problem to use statics for temporary data in a way that makes efficient use of memory - and of course, in a way that is safe and correct. I'm sure Walter Banks could tell you all about it (though he might consider it a trade secret).

What do you mean by "thunking" in this context? The term has several meanings, as far as I know. If your answer is going to take more than a dozen lines (you are better known for your in-depth explanations than your short summaries!), it should probably be in its own thread.

mvh.,

David

:-).

Let's just leave it here as an agreement to disagree, then. I believe you are sincere in considering what I've already written, probably doing me more justice than I may deserve. But since you still cannot gather, I have to assume it's my fault in writing poorly and that the effort may require more time than I care to have for a semantic only of some small value. We've already made more of it than the newsgroup's time is worth. So I'm fine leaving the topic behind and just saying that I have used the semantic before to good use and sometimes miss it in c. There's no payoff here. Let's just leave it as a mild disagreement over a not-terribly-important issue.

I think they simply didn't see it, before. A failure to imagine as well as they should have done. Nothing more.

I'm sure he knows a great deal more about the complexities than I do, so you are most certainly right that he could tell me about the subject. I've never claimed expertise here. Merely the ability to observe specific failures when I see poor results from poorly imagined solutions.

There are some excellent discussions already available. See, for example, the Metaware C (and Pascal) compiler manuals and their implemention of an iterator semantic, as well as their extensive discussion (with well-made examples) of its abundant benefits. There is also a somewhat different, but also interesting discussion made by Randall Hyde in The Art of Assembly manuals he generously put together some years back. (Don't worry yourself thumbing through Microsoft's use of the term.)

...

The following case below is not nearly as well thought out, syntax wise, as Metaware's implementation (in other words, don't mistake it as a complete syntax designed by experts) but it may get the seed of a point across.

for ( p in Primes( 20, 70 ) ) printf( "%d\n", p );

The code for Primes() might be, in short-hand, something like this below. (Please excuse my abuse of basic knowledge about prime numbers by using an increment by 1 in the for loop or any other tests for starting or ending on an even number only because I want to keep the code with as few lines as is reasonable to make the point. The idea is the point, not the implementation here.)

integer Primes( int a, int b ) { int i; for ( i= a; i

[...]

Yes, lots of manual attention and review is needed as the code which is to be relocated to RAM can not depend on anything else. Most compilers and linkers will happily use any variable or routine elsewhere in the project.

Would be a good idea to reset the stack pointer at the start of the RAM code. Globally disable interrupts too as sometimes its hard to individually disable every single interrupt source on an MCU. Also do not forget the interrupt vector table (or whatever means is used to route interrupt service routines).

Okay. I'm not sure whether I am misunderstanding you, or disagreeing with you, but I'm happy to leave it for now. Maybe the topic will turn up another time, and it will all suddenly become obvious.

Ah, what you are talking about is often called a generator (for example, in Python or JavaScript), or perhaps a closure. A generator is somewhat like a lazily evaluated list, although it could be generalised (for example, do the parameter values have to be the same for repeat calls as they are for the initial call?).

I've used generators in Python - they are a very nice way to solve some kinds of problems. Unfortunately, they are not easy to implement cleanly in a stack-based language because a general implementation requires that the generator (or "thunk", if you prefer) has its own stack for arbitrary local variables and state. Thus most languages that implement generators use garbage collection.

You can get some of the features of generators in C++ using a class to encapsulate the generator's state in class data. The newer lambda syntax makes it a little neater, and comes somewhat closer to generators or closures - but there are limitations. You can't implement them in general without substantial helper code (as seen in boost's libraries) or a major change in the structure of the language (including garbage collection).

In short, generators (and closures) are a very nice high-level concept - you need a high level language (or, somewhat ironically, assembly) to use them, and C / C++ are not suitable.

I only introduced it as an aside, to start. It's just not that important.

You are way off the reservation, already. Let me suggest you think about the _implementation_ for a moment. Maybe that will clarify what I'm pointing towards. For one thing, garbage collection has _NOTHING_ whatever to do with it. If you think so, you are far off-point.

I recommend that reading I suggested. Take the AofA one first. It's easier to get ahold of. It gets into the details of implementation, but does NOT deal with it as a c level concept. So you will have to fend for yourself there until you can get ahold of Metaware docs. (Or, I might be tempted to copy some of them for this purpose.)

Anyway, I can see I've sent you spinning in the wrong direction. Take a breath, read AofA on the topic of thunks and the nearby related chapters to it. That should provide an idea about implementation. Not the _whole_ idea, by the way. As it might be done in c, it involves the concept of nested functions (which you clearly don't yet see) without the use of the specific syntax you are used to seeing for them (it's entirely hidden at the c language level, but explicit at the assembly level.) If you _see_ this much, we are probably on the same page.

Jon

I don't have these books and manuals you are referring to, nor do I have easy access to them. If you have web links of interest then I'll happily look at them - but I am not going to find and order books just to read a few pages. This discussion is interesting, but there's a limit to what is a practical and appropriate use of time and money for a discussion.

Nested functions are perfectly possible in some extensions to C - in particular, gcc supports them (since gcc also supports Ada, which has nested functions, much of the gcc structure already supports nested functions, and thus the C and C++ front-ends can get them almost for free).

Nested functions, C++ classes, the new C++ lambda syntax, etc., are all ways to implement a limited form of generator or iterator. Compiler extensions can be used to make a nicer syntax, and to automate some of the manual work involved. But without some sort of multiple stack system or garbage collection, you have serious limitations. I don't mean to say that these ideas are not useful despite the limitations - just that you cannot add proper flexible generators to a language with the sort of structure of C or C++ without fundamental changes to the way the language works - the compiler would need to be free to allocate (and free) dynamic memory as needed, rather than through explicit malloc / new calls.

It could well be that what you call "thunking" really means "generators with various limitations", in which case you are right that garbage collection is not needed, and it's reasonably easy to figure out several good implementations. But the term "thunking" is not a well known or well-defined expression, and is used in many different ways by different people - I have no idea how the author of a particular book you've read happens to use it.

To look at some more general generators, and see why they can be used much more freely in a language like Python than they can in C or C++, let's vary your Primes function, using Python syntax so that we have actual working code:

def IsPrime(i) : if i < 2 : return False for j in range(2, int(math.sqrt(i)) + 1) : if (i % j) == 0 : return False return True

def Primes(a, b) : i = a if (i == 2) : yield i if (i % 2 == 0) : i = i + 1 while ( i

Go here:

formatting link

Then download and read all of Volume 5.

formatting link
formatting link
formatting link
formatting link
formatting link
formatting link
formatting link

I apologize for not doing this earlier. I had expected that you already knew about AofA and it's availability on the web. Had I known you didn't know about it, I would have immediately provided you the links. Again, my sincere apologies for not doing this earlier.

Yes, but as a general rule I don't always have the option of using gcc. Customers sometimes already have existing tools they want used, for example. There are other reasons, too. So it's not a general solution. Just an interesting one.

Hopefully, the above will tell you more.

In fact, that's what is vital. In the implementation done by Metaware's compilers, it was very well handled and the implementation was quite general and nestable to any depth without the programmer worrying over details such as that. It's all simply kept as stack frame contexts, just as normal functions do. The difference is that a thunk is used, instead, to move back and forth in order to preserve the stack context while the iterator remains "live." Once the iterator completes, though, the stack is unwound in the usual way and the context disappears just as you would expect for any function call.

Things do not get difficult. I've used metaware's compiler tools and there was NO difficulty involved. It's _exactly_ like using c, except you've got a wonderful additional semantic to handle, in beautiful and efficient ways, concepts like walking graphs. The idea of "data hiding" is expanded to also now include "algorithm hiding," but in a very light weight fashion that is entirely consistent with the c worldview.

Got it. I understand your point now about garbage collection

-- makes sense. But the way Metaware handles it is beautiful and doesn't require any of that. It's entirely handled within the standard c style program model with a single stack and all the usual, normal stack frame elements. The body of a for loop is placed into a separate, nested function within the body of the enclosing function. The iterator is called using all the usual means, but includes a pointer to the body. The iterator may itself call any number of other functions, as well as other iterators if it likes, which may be nested down the stack to any depth you want. When a yield takes place, it is really a call to the for-body nested function but with the stack frame pointer set to the enclosing function so all the usual local variables are appropriately accessible off of the base pointer reference that all c compilers may normally use. The nested function returns rather normally, restoring the frame back to the down stream end of the stack. If the for-body temporarily stores on the stack, it does so at the end of course and obviously must restore it before returning. But that's just basic, anyway.

So let me think about this for a second. Passing a generator would involve being able to return not only a pointer to code, but also its entire current context (and any such context of all activation records still active at the time?) In other words, it's not just a generator at its initial point, but one that may have already been used for a bit but hasn't yet completed and so it can be returned to a caller for more continued use? Interesting, and I gather the additional value here.

However, as an _embedded_ programmer usually working on tiny micros, not workstation-competent board-level systems, I'm focused upon very modest but very useful extensions where I _know_ I have good application and where I don't have to pay for it with a significant change to the existing models I have grown to know well and fully understand and trust.

That said, I'd be interested in seeing how to implement something like that which would work in ways where the run-time execution duration is entirely predictable and invariant (knowing, obviously, the initial conditions for the generator.) I think you hint towards this, but I'd need to see a specific implementation.

In the meantime, you might look at the PDFs I've referred you towards. They aren't that long and are quite detailed. They do NOT show you the implementation used by Metaware, but I can talk about that.

Jon

Thanks - that makes a /huge/ difference! Of course, now I just need the time to read it. As I've only had a brief look at it (I read most of the chapter on thunks), I may be misjudging it here, but I have difficulty seeing the relevance of the book at this time. I can see the point of a DOS assembly book long ago, and I can see the point of a general book on assembly for multiple architectures. But I can't think of any reason (other than for fun) why anyone would write software in assembly for the x86 - and certainly not for Windows. There are certainly times when you might want to /use/ assembly on an x86 - speeding up critical loops, for example - but not to write entire programs. The HLA concept strikes me as a waste of time in this day and age.

Having said that, some of the concepts (such as in the chapters you have indicated) are interesting and have wider applications. Coroutines are useful devices - it's just that the implementation details of how to use them with HLA x86 assembly are irrelevant to reality. Had the author shown how to use them in C, Java, Python, or even in an artificial HLL, it would have been more useful.

Anyway, now I see what you mean by the term "thunk" - and it is clear from the book that these are limited devices that are basically equivalent to a C++ class with initialisation of private data values and a single method (or alternatively a C function that takes a struct pointer). Useful, but hardly revolutionary. Your proposed syntax for them in C is, however, neat and elegant - that would be a useful addition to the C language.

Agreed. I use various gcc extensions if I think they improve the code (with the emphasis here on improving the source code rather than the target code - that's a bonus). I haven't used nested functions - they often make code less readable because it is often unclear where different functions start and end.

I agree here that such a syntax and compiler-aided handling of the details would give you a very nice way to use these "thunks" - much more convenient that doing things manually in C or C++. I suspect you could get a fair way with "normal C" using a system similar to Adam Dunkels' protothreads - but integrating it into the language would be best.

The Metaware implementation, as far as I can see, is limited to situations where the thunk's frame can be allocated on the stack (or possibly as a statically allocated region). That is certainly the situation described in AofA. That is, of course, an entirely reasonable limitation for an extension to C.

My view of such concepts has come down from higher-level languages like Python (and also functional programming languages), in which you have much more general capability in how you work with function-like objects and closures. From that angle, these "thunks" look limited, because you need a compiler and run-time that handles dynamic memory (typically some sort of garbage collection, but that's not absolutely necessary) to implement the capabilities I assume. But when you are thinking of these as an upwards extension of C, I can see these being a very useful addition to the language.

That's correct. You can see here how this requires the generator's local frame to remain valid after the function that created it has exited - that means it has to exist outside the main stack. Thus for this sort of thing to be handled directly by the language and the compiler, rather than through explicit "new" or "malloc" calls, the compiler has to have a direct understanding and control of dynamic memory.

I agree here - and I would appreciate the addition to C of the sort of capabilities you have been describing. For embedded systems, it is important to be able to understand the implementation for the code you write, and that is possible for "thunks" as you have described them. On a PC, it (typically) doesn't matter if the software takes a few extra MB of run space, and runs through a byte code virtual machine - thus I program in Python and take advantage of the language's power to write shorter code.

Assembler is 100% flexible compared to any HLL, even C, so sometimes it makes life easier.

While you probably wouldn't write applications in it, there are types of programs which do have a big proportion of assembler (in my case, these are interpreters).

Hyde's HLA is not for everyone, but I use of form of HLA (inline assembler within a HLL) which makes writing large amounts of assembler much less painful.

And, if you are working on a language product which generates assembler code, then you need to understand how it works even if you are not manually writing the code yourself.

Bartc

Sweet. Now if we can just convince those c-standard folks!

By the way, just so you know, Metaware's founder (one of them, at least) was Dr. Frank DeRemer. He's known well for his Ph.D. thesis on LALR parsing, "Practical translators for LR(k) languages," MIT, Cambridge, Massachusetts, 1969. He and Dr. Tom Pennello went on to write some tools for compiler compilers and an article called "Efficient Computation of LALR(1) Look-Ahead Set," TOPLAS, vol 4, no 4, in October

1982. Which was around the time, I think, that Metaware was becoming a reality of sorts.

I very much enjoyed my conversations and learned a few things from them (especially Tom), back around that time. They were generous with their time and help and willingness to teach.

If you wonder how a paper that doesn't use LALR in its title is about that, take a look at the wiki page here:

formatting link

Dr. DeRemer invented LALR.

Jon

This last paragraph makes an excellent point, regardless of how one may take the rest of what you say (which I also consider well-spoken.)

Jon

I am not saying there is no place for assembly - for small systems, assembly can still be a good choice (and I have done a lot of assembly programming on small systems through the years). There are also parts of large systems that are best done in assembly. And of course you should understand assembly when working with embedded systems, and as you say, a compiler writer is going to have to be an assembly expert. But the days of writing large applications in x86 assembly for PCs (this book is targeting x86 assembly for windows) are long gone, bar a few specialist applications or keen enthusiasts.

Embedded assembly in a C program becomes a lot more manageable when the assembler and C compiler share the same symbol table. Getting rid of address mangling between C and asm and giving the asm full access to C's symbol table makes it a lot easier to add embedded assembler to an application.

Regards,

Walter..

-- Walter Banks Byte Craft Limited

formatting link

That is not HLA. HLA is IMHO a bizarre concept. It is abstraction added on top of an assembler. Assembler is the pinnacle of concreteness. If you go for assembler, you should complement it with macro's. Macro's may be a pain to use, but they are extremely flexible and the result remains horribly concrete.

I don't think this is just an opinion, and I would add this to Wikipedia, but I have some stakes here.

1] I have a Forth system, where macro's help me to have the same source 16/32/64 bit, linux/msdos/mswindows/standalone. It is legitimate for a language implementation to be written in assembler (maybe complemented with parts written in the language itself.) (The alternative is using C as a portable assembler, but that leaves C itself to be written basically in assembler.) 2] I also have (dis)assembler that doesn't hide the difference (i86) between move-from-register-a-to-b and move-to-register-b-from-a MOV BX,AX (No not LEA. ) which makes it suitable for reverse engineering stealth viruses.

Also this notion should be more prominent on Wikipedia pages. Sometimes it gives the impression that those using assembler are behind the times instead of supplying the foundation for the whole IT industry.

--

Albert van der Horst, UTRECHT,THE NETHERLANDS Economic growth -- being exponential -- ultimately falters. albert@spe&ar&c.xs4all.nl &=n http://home.hccnet.nl/a.w.m.van.der.horst

Join the Discussion

Have something to add? Share your thoughts — no account required.

Didn't find your answer?

Ask the community — no account required