|
|
|
|
|
by billforsternz
4844 days ago
|
|
Well I certainly salute your passion. I am not nearly dedicated enough to go through this point by point. The stack issue comes down to this; C uses the assembly (i.e. machine) stack. It is an almost ridiculously simple mechanism ideally suited to pass parameters and allocate 'automatic' (yes this is a C term too) data. Avoid large aggregates and arrays on the stack because stack space is limited. Providing you adopt a conservative approach, you never have to worry, 90% of a 2K byte stack in a small embedded system is typically safety factor/headroom. My personal view is that C offers a perfect tradeoff between simplicity and capability, it has a magical quality that has made it the most important single computer language for nearly 40 years and on into the forseeable future. Increasingly its importance is as a layer that more programmer friendly technology sits upon, but it's no less important for that. I've read that the difference between chess and go (the oriental game, not golang) is that on Alpha Centauri if little green men play a game that resembles chess, they will almost certainly play a game identical to go. Go is simple enough it is almost inevitable. For me it's almost the same thing (I stress almost), with computer languages and C. One final point; C syntax is ultimately a matter of taste. If you find this to be a completely obvious, correct and straightforward way of doing a non-overlapping C string copy; while( *src )
*dst++ = *src++;
*dst = '\0';
Then you 'get' C. If you find it a confusing monstrosity, maybe C isn't your language. |
|
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++.