I have several very long subroutines, 1500+ lines each, which consist of 20-50 discrete portions. For stack purposes, I don't wish to make subroutines out of each portion, but for clarity I wanted to separate the portions. I moved each portion, typically the body of an if or while loop, into its own .c file and #included them in place, changing if (a > 100) { statement1; statement2; ... statement 50; }; // if into if (a > 100) { ; main.c, line 30 #include body1.c ; main.c, line 31 }; // if ; main.c, line 32 with file "body1.c" containing the statements, without any subroutine syntax. statement1; ; body1.c, line 1 statement2; ; body1.c, line 2 ... statement 50; ; body1.c, line 50 The compiler does what it is supposed to do as far as the executable is concerned; it generates the correct opcodes as if it were all inline in a single file. The problem I have is with emulation, but the reason can be seen by looking at the generated list file. The line number information for the included .c files is inappropriately constructed. In the above example, the listing file attributes the HLL statements to main.c, lines 30, 1, 2, ..., 50, 32. What would be desired would be to attribute them to main.c line 30, body1.c lines 1, 2, ..., 50, and then main.c line 32. When enulating, the debug information for HLL presents lines from the parent file, instead of from the included file, making it difficult to debug within the included files. Has this been seen before? Is there anything that I can do? Is there anything that you can do?
"I have several very long subroutines ... For stack purposes, I don't wish to make subroutines out of each portion" A function's stack usage has nothing to do with its length! In fact, C51 doesn't even use the stack for locals nor parameters. "...typically the body of an if or while loop..." this suggests to me that your function calls aren't even deeply nested - so you should have no stack worries at all! I think your function aversion is misguided - try it and see! "Has this been seen before?" Yes - this is the way the preprocessor works. The preprocessor does its work before the compiler ever gets to see your code (hence "pre-") - so the compiler doesn't actually know what came from #included files & what came from your "main" source file. "Is there anything that I can do?" Not if you insist on doing this! Another (one of many) reason to use proper functions!
I agree, stack usage has nothing to do with the length of a function. However, if I were to replace if (a > 100) { statement 1; statement 2; ... statement 50; }; // if with if (a > 100) { body1(); }; // if void body1(void) { statement1; statement2; ...; statement50; }; Then there would be a stack usage involved in the call to function body1(). Is there a way to have the preprocessor generate a flat file, and then have the compiler operate on the flat file, so that the line references would all be to sequential lines in the flat file, and the HLL references in the emulator would be reconcilable?
"Then there would be a stack usage involved in the call to function body1()." Yes, but why is that such a big issue for you? If it really is such an issue, should you be using 'C' at all?
The "PREPRINT" compiler directive may get you the preprocessor output. (The manual says "with macros expanded", so I'm not certain if that includes all preprocessing.) If not, then you could use another C compiler or external preprocessor such as m4 to produce the flat file for later processing by the compiler. The #line directive allows you to reassign line numbers and source file name, so you might be able to trick the debug information by including that directive in your sub-files. A function call to a void (void) function will cost you two bytes of stack for the return address (and nothing else). Interestingly, I don't see an inline directive, nor does the compiler seem to automatically inline single uses of static functions. Perhaps Jon can shed some light there. If those files use temporary local variables, it might actually save you some space to declare them as functions. If each of 30 routines needs a 16-bit temporary, then the parent routine will allocate 60 bytes. If you make 30 calls, the overlay processing will figure out that it doesn't need all 30 temps at the same time, and overlay them, for only two bytes of usage. If it's absolutely crucial that you not affect the stack pointer for some reason, you should be aware that sometimes the code generator will sometimes temporarily use a few bytes of stack for its own purposes, on top of any stack it may use for calls to compiler internals. Your code is probably already using more than two bytes of stack to implement that function. If you don't like function calls, you won't like what happens to the code if you turn the optimizer-for-size loose. It's pretty ruthless about hunting down any similar instruction sequences it can find longer than a procedure call/return, and turning them into functions with ACALL / RET. A rough rule of thumb from my code seems to suggest about two bytes of instruction per LOC. Four bytes of function call overhead is fairly small compared to the 100+ bytes to actually perform the action.
I will try your suggestions. The overlaying process leaves me with mixed feelings. I have found that it is usually good at recognizing and overlapping temporary variables in small scopes inside a function. A large issue I do have with the overlay process can be summed up with this example: void main(void) { if (test) { unsigned char i; statements; sub1(); } else { unsigned long j; statements; sub2(); }; // if } void sub1(void) { unsigned long k; statements; } void sub2(void) { unsigned char l; statements; } The overlay process will overlay mutually exclusive variables within a subroutine, and will overlay mutually exclusive subroutine variables, but it will not overlay a subroutine with its parent if the subroutine call was made at a point that is not the maximum scope. i and j are mutually exclusive variabales within a subroutine. They are overlayed within the same 4 bytes. k and l are in mutually exclusive subroutines called by main. They are overlayed within the same 4 bytes. This stub uses 8 bytes, i and j overlain at offset 0 and k and l overlain at offset 4. However, sub1() uses k at a point when only 1 byte (i) is used, while sub2() uses l ata a point when 4 bytes (j) are used. The variables could be compacted as var offset length i 0 1 j 0 4 k 1 4 l 4 1 This would use a total of 5 bytes, compared to the 8 of above. A part of my issue with trying to accomplish what I am stems from my desire to minimize stack use (which uses internal memory) while overlaying temporary variable usage as best I can. Thus, I have several instances where multiple mutually exclusive scopes (if and switch statments) with at different levels of memory usage force me to have a single, subroutine, instead of multiple parallel and/or nested subroutines.
"The overlay process will overlay mutually exclusive variables within a subroutine" The overlaying works by block scope - which is not limited to just functions. eg,
int i,j : do stuff with i : do stuff with j :
{ int i; : do stuff with i : } { int j; : do stuff with j : }
I appologize for not being concise with my wording. Yes, that is how the overlay works. The point I was trying to bring up, however was in the use of functions in different blocks.
{ char i; long k; } { long j; char l; }
{ char i; Sub1(); } { long j; Sub2(); } void Sub1(void) { long k; } void Sub2(void) { char l; }
View all questions in Keil forum