Path: ...!weretis.net!feeder8.news.weretis.net!newsfeed.bofh.team!paganini.bofh.team!not-for-mail From: Waldek Hebisch Newsgroups: comp.lang.c Subject: Re: Top 10 most common hard skills listed on resumes... Date: Sun, 8 Sep 2024 00:05:59 -0000 (UTC) Organization: To protect and to server Message-ID: References: <87o75bwlp8.fsf@bsb.me.uk> <871q27weeh.fsf@bsb.me.uk> <20240829083200.195@kylheku.com> <87v7zjuyd8.fsf@bsb.me.uk> <20240829084851.962@kylheku.com> <87mskvuxe9.fsf@bsb.me.uk> Injection-Date: Sun, 8 Sep 2024 00:05:59 -0000 (UTC) Injection-Info: paganini.bofh.team; logging-data="2249381"; posting-host="WwiNTD3IIceGeoS5hCc4+A.user.paganini.bofh.team"; mail-complaints-to="usenet@bofh.team"; posting-account="9dIQLXBM7WM9KzA+yjdR4A"; User-Agent: tin/2.6.2-20221225 ("Pittyvaich") (Linux/6.1.0-9-amd64 (x86_64)) X-Notice: Filtered by postfilter v. 0.9.3 Bytes: 10064 Lines: 189 Bart wrote: > On 07/09/2024 02:44, Waldek Hebisch wrote: >> Bart wrote: >>> On 06/09/2024 11:19, Waldek Hebisch wrote: >>>> Bart wrote: > >>> (You can balance it out by by requiring ASSIGN(&A, &B)!) >> >> This would not work in general, as I wrote it, the following are >> valid: >> >> assign(&a, 42) >> assign(&a, a + 1) >> >> but the second argument has no address, so your variant would not >> work. > > I believe that C's compound literals can give a reference to a+1: > > #include > > void assign(int* lhs, int* rhs) { > *lhs = *rhs; > } > > int main(void) { > int a=20; > > assign(&a, &(int){a+1}); > > printf("a = %d\n", a); > } > > > The output from this is 21. Yes, that would work. But you use here complex language feature, rather unattractive if one want to use assign as a basic construct. >> You can use stack machines to get reasonably simple definition of >> semantics. But still slightly more complex than what I outlined >> above. And code for stack machines is unpleasent to optimize. >> In a compiler for a language where official semantics is stack >> based the first things that compiler does is to track few items >> on top of the stack and match them to sequences like >> >> push a >> push b >> call op >> >> Once sequence is matched it is replaced by corresonding operation >> on variables. If this matching works well you get conventional >> (non-stack) intermediate representaion and reasonably good code. >> If matching fails, the resulting object code wastes time on stack >> operations. > > (My stack IL is different. The stack is a compile-time stack only, and > code is scanned linearly during code-generation. Roughly, the 'stack' > corresponds to the machine's register-file, although in practice > register allocation is ad-hoc. OK, that is another way to work around slowness of the stack. But you get into trouble when you have more items on the stack than number of available registers. >> Best Forth compilers >> have sophisticated code to track stack use and replace it by >> use of registers. Other Forth compilers just accept slowness. > > Then you no longer have a language which can be implemented in a few KB. > You might as well use a real with with proper data types, and not have > the stack exposed in the language. Forth code can be very cryptic > because of that. First, it is not my goal to advocate for Forth use. But I think it is interesting to understand why things were done in the past. Concerning exposed stack, Pop11 has exposed stack. 5 line executive summary could be the same as of Forth. But language is feels quite different. Pop11 uses classic infix syntax, and variable access puts "value" on the stack. I put "value" in scare qoutes because most of Pop11 data items are composite and live in memory. They are accessed using their address and that is what is put on the stack. But semantically you deal with values and addresses are normally hidden from users. If you wish you can ignore the stack. Presence of the stack is visible in examples like below: : 2*3 => ** 6 : 2, 3, *() => ** 6 the first line is infix form, '=>' oprator prints what is on the stack (but you cat treat it as "print current result"). In the second line two numbers are pushed on the stack and then there is call to multiplication routine. Parser knows that '*' is an operator, but since there are no argument '*' is treated as ordinary identifer and as result you get multiplication routine. Like in other languages parentheses mean function call. Up to now this may look just as some weirdness with no purpose. But there are advantages. One is that Pop11 functions can return multiple values, they just put as many values as needed on the stack. Second, one can write functions which take variable number of arguments. And one can use say a loop to put varible number of arguments on the stack and then call a routine expecting variable number of arguments. In fact, there is common Pop11 idiom to handle aggregas: 'explode' puts all members of the aggregate on the stack. There are also constructor function which build aggregates from values on the stack. Coming back to Forth, you can easily add infix syntax to Forth but Forth users somewhat dislike idea of using infix for most of programming. My personal opinion is that Fort was good around 1980. At that time there was quite simple implementation, language offered interactive developement and some powerful feature and there were interesting compromise between speed and size. Namely, Forh code tended to be smaller than machine code and deliver speed lower than machine code but better than some other alternatives. Now, interactive developement is done on bigger machines, so small size is not so important. Traditional Forh uses so called threaded code, which has machine word sized units. With 16-bit words that leads to relatively compact code. With 32-bit words you double code size. And on 64-bit machines this is quite wasteful. > What started the subthread was the question of which HLL goes between > ASM and C (since someone suggested that C was mid-level). Well, for me important question is how much work is due to tools (basically overhead) and how much deals with problem domain. Since computers are now much heaper compared to human work there is desire to reduce tool overhead as much as possible. This favours higher level languages, so probably most recently created languages is at higher level than C. However, in sixties and seventies there were pack of so called algorithmic languages or somewhat more specifically Algol family. I would say that C is close to the middle of this pack. As a devils advocate let me compare typical implementation of early Pascal with C modern C. In C variables, including aggregates can be initialized at declarartion time, early Pascal did not allow this, so one had to initalize variables by code. In C function definition gives argument names and types, even if there is earlier prototype. In early Pascal, if you gave froward declaration (equivalent of C prototype), you _had_ to omit function parameters (and their types). That significantly decreased readabilty of such functions. In early Pascal one had to declare all local variables at the start of a fucntion. C has block structure, so one can limit variable scope to area where variable makes sense. More importat, one can declare variable when there is sensible initial value, which significantly decreases chance of missing/wrong initalization. Many early Pascal implementations did not have conformant arrays (present in original Wirth Pascal). That meant that all arrays had to be of size known at compile time. From C99 C has variably modified types, which allow writing functions accepting arrays of varying sizes allocated elsewere. Let me also mention C preprocessor: it allows programmer to effectively define simple language extention, so that language better fits to problem domain. This in not available in standard Pascal. Of course, C has buch of low level features as casts and pointer arithmetic. However, with variably modified types pointer arthmetic is no longer needed and casts are system programming feature not needed for normal programs. So if presence of those bothers you the simple solution is not to use them (of course C standard defines array access in terms of pointer artihtmetic, but one can avoid explicit pointer artimetic and allow only array access in the sources). Early Pascal has a bunch of features not present in C, but one can reasonably consider modern C to be higher level than early Pascal. And _a lot_ of early languages were at lower level than Pascal. > People suggested ones like BLISS and Forth. > > I remarked that a proper HLL would let you write just A to either read > the value of variable A, or write to it. Eg. A = A, without special > operators to dereference A's address. You are looking at superficial things. Forth is extensible, fetch appropriate extention from the net and you can write expressions in infix form using normal syntax. IIUC Bliss has powerful preprocessor, I am not sure if it is powerful enough to transform expression without dereferences into ones with dereferences, but even if it can not do that extensibility allows to write clearer code closer to problem domain. ========== REMAINDER OF ARTICLE TRUNCATED ==========