Gerry Quinn wrote:
[I said:]
[about "self-modifying code", under which heading Gerry apparently
includes code generated by Lisp macros. (And C++ templates? And the
C preprocessor?)]
[Gerry:]
It might or might not. What I'm pointing out, after all, is that the
details of how the modified code is generated doesn't count.
I don't think it's at all obvious that the details don't
matter. One of the reasons for avoiding self-modifying code
is that programs that use self-modifying code are hard to
understand. The way in which the code is generated can
make a difference to how hard the program is to understand.
NOP; NOP; NOP; NOP is code that is modified. Or if you object to this I
could change it to something that does something more interesting, or
would if it were not erased and replaced by alternative code.
The fact is it makes no difference. It is all about the level of the
generated code.
I don't understand that last statement. What do you mean by it?
As I hinted in the grandparent of this article, it would
be helpful if you'd explain exactly what you consider to
be the reasons for disapproving of self-modifying (or, more
generally, generated) code. There are several possible
such reasons, and some of them but not others apply (at
least in some measure) to all code generation. Also,
some of them are (at least in some measure) bogus reasons.
I'd say that there are several different questions that
need to be answered about an instance of code generation
before you can tell whether it's a Bad Thing or not. For
instance, and this is far from an exhaustive list,
- Does it happen at compile time or run time or what?
- Does the code responsible for generating it make
the function of the generated code clear? Preferably,
just as clear as the function of the normally-compiled
code in the program?
- Is any other code being replaced by the generated code?
- What determines whether or not the code gets generated,
and *what* code gets generated?
- Does the same code get modified several times during
the execution of the program?
- Is it necessary to understand exactly what's going on
with the code generation, in order to understand the
program?
- What safeguards are in place to ensure that the
code generation doesn't stomp on code that isn't
meant to be modified?
So, for instance, if you're writing code that looks like
this then it's pretty majorly bad. (My apologies to anyone
reading this in, e.g., comp.lang.lisp who may be offended
by the choice of C syntax...)
int do_something(int x) {
/* blah blah blah */
}
void hack_it(int a, int b, int c, const char * s) {
char * p = &do_something;
char t;
/* write preamble: */
*p++ = 0x90;
*p++ = 0xf3;
/* write main code */
while ((t=*s++) != 0) {
switch (t) {
case 'a':
*p++ = 0x83;
*p++ = 0x1f;
*p++ = a & 0xff;
break;
/* ... other equally hideous cases .. */
}
}
/* write postamble */
*p++ = 0xaa;
*p++ = 0xdc;
do_something(b);
flush_icache(&do_something, 0x100+(char*)&do_something);
}
Spotting all the things that suck about the crawling horror above
is left as an exercise for the reader. There are rather a lot.
But there are plenty of other things that can reasonably be called
code generation, and they don't all share all those problems.
Now, you've been describing Lisp macros as "self-modifying code".
I don't know how much you know about Lisp macros, but they are
certainly an *entirely* different kettle of fish from the nasty
C code above. Different enough that I can't imagine why anyone
would call them "self-modifying code".