|
So, with just 2KB, that machine stack is not the
stack (of dynamic descendantcy', that is, the
conceptual stack of routines called but not yet
returned) for automatic storage. Good to know. So,
if only pass pointers or 'element' variables, then a
2KB stack for parameter lists should be okay for
small to medium programs unless the programmer
actually believed his computer science course that
said that recursive routines were good things! Yes, for your code example, I 'get it'! It's cute!
No doubt it's cute. So, to get 'full credit', dst and src are 'strings',
that is, essentially just pointers. Since C
pointers have a data type with a length, here the
data type of these pointers is byte, or character,
or some such with length 1 byte. Starting off, since src points to a C string that
obeys the C standard of null termination, we know
that the string has exactly 1 null byte, its last
byte. So, for any byte except the last one, it is
not null. So, if the string has length more than 1,
then as we enter the While, src points to the first
byte of the string (or at least the first byte we
want to copy); * src is that byte; and * src is not
null, that is, is not 0, that is, tests as True for
the While (need to know that 0 tests as false and
anything else tests as true). So we execute the statement following
the While. That statement says copy byte * src to byte * dst and,
then, before considering the semi-colon and before
collecting $100, increment both src and dst by the
length of their data types, that is, by 1 byte. So,
now src points to the next byte in its string and
dst points to the target of the next byte in its
string. Then we return to the While and do its test
again. If we have more bytes to move, then we just
rinse and repeat the above. Else src points to the
last byte of its string which has to be the null
byte so that the byte itself, * src, is the null
byte, that is, 0, that is, False in the While
statement. Then we leave the While statment, that
is, move past its semi-colon. So, net, the While
statement moves all the non null bytes we want
moved. Of course when get to the next statement,
dst points to the last byte of its string, that is,
the byte that is to be its null byte (just why that
byte is not null already is possibly an issue). At
any rate, we want that last byte to be the null
byte, so that last statement so assigns that last
byte. For more, 'src' abbreviates 'source' and 'dst'
abbreviates 'destination'. So, yes, it's possible to describe this stuff in
English. So, there's a problem, a significant problem:
I 'documented' your code. Okay, but your code
is for a string copy. There should be some
documentation, but it should be in documentation
of a string operation for the language. That is,
even just for the documentation, the strings and
the copy operation need to be 'abstracted' to a
higher level where they can be documented and
learned there and set aside the need to
document in the code. Of course, I would write such a copy loop, assuming I wanted to use a loop, using
Fortran's Do-Continue, PL/I's Do-End, VB's For-Next.
I don't recall the Algol syntax. The PL/I syntax is
the same as in Rexx which I use heavily. Of course in PL/I and VB, I would use the substring function
instead of a loop. And I
would fear that too much usage of syntax as sparse
as this example here would be more error prone. And
for more complicated operations, I would fear that
neither God nor the programmer understood the code.
I can understand that on some processors with some
compilers, that C syntax could lead to faster code,
but I'm not thrilled about digging into x86
assembler enough to be sure. Also now it's tough to
know what fast code is due to out of order
execution, speculative execution, parallel
execution, pipelines, three levels of cache, the
cache set associative, and cache line invalidates
when have several cores. But, computers are so fast
now I don't much care, and if I did care I would
notice that making such code faster would not make
the code in the runtime library or the operating
system faster and, thus, might not be able to do
much for actual performance of my application. I don't really mind your example; it's actually not
sparse enough to be a serious problem. But I'm not
thrilled by the example because I don't take
pleasure in that clever sparsity and, again, I'm
afraid that it could result in bugs that could hurt
my business. Then there's the issue, I regard as significant,
that with that code the C compiler is very short on
the 'meaning' of what is being done. Or, you and I
can look, read, guess, and agree that we are doing a
copy of the tail of one C string to another (or
possibly the same if initially src equals dst)
string. Okay, you and I can guess that. But the
poor compiler can't, and if it tries then I might
get torqued when in that loop actually I'm not
working with C strings but doing something else.
So, then, the compiler will have a heck of a time
checking string lengths and not writing on the wrong
storage. So, I'd rather have strings as a higher
level construct, than just a pointer to some storage
from malloc(), so that the language can help me
debug my code. I've programmed so many errors in
array bounds and string lengths that I don't want to
be without some good checking at runtime, at least
in some 'debug' mode. Next, for being 'fast', on at least IBM 360/70, etc.
computers, actually that code sample would be slow!
Why? Because that instruction set has a single
instruction to copy all the bytes in a range of
sequential virtual addresses. So, if the compiler
knows it is working with a string and knows it is
compiling for that instruction set, then it can
replace the whole loop with just one instruction. There was an old remark in the IBM PL/I program
logic manual on execution speed: People could
complain about PL/I being slower than Fortran. But
if PL/I were used at all carefully, then it was
faster than Fortran, and one of the main reasons was
that PL/I then but not Fortran had strings in the
language. So, Fortran programmers wrote collections
of string handling functions/subroutines, and the
internal logic was much as in your example, move one
byte at a time. And as in the standard C library,
do this by calling a function/subroutine with its
overhead. PL/I's compiled code for strings was
in-line and blew the doors off anything in Fortran.
For today, and for similar reasons, Visual Basic
.NET has a chance to be faster in string
manipulations than C code such as in your example.
Further, it's super tough to do something with VB
strings that would mess up memory. I don't find your example "a confusing monstrosity",
but I greatly prefer to bet my business on VB
instead of C/C++. |
By little example is basically the strcpy() standard facility. Maybe a better example would be a construct that (roughly) could replace memcpy();
This sort of thing just appeals to me as being simple and obvious computing - there's no cleverness to it - and certainly no need to break it down exhaustively to understand it. I think whether this sort of thing appeals might have something to do with prior experience - in my case as an engineer and assembly language programmer;The equivalent to my memcpy() snippet on the original x86 machines was simply this;
Put the count in cx, the source ptr in si, the dest ptr in di and the REP prefix will repeat the MOVE STRING BYTE instruction and decrementing cx each time until it hits zero.