Author Topic: Pointer confusion in C -language  (Read 21958 times)

0 Members and 1 Guest are viewing this topic.

Online gf

  • Super Contributor
  • ***
  • Posts: 1173
  • Country: de
Re: Pointer confusion in C -language
« Reply #75 on: June 25, 2021, 10:44:09 pm »
Sorry, just responded to the prevous message w/o reading the whole thread. Seems indeed that it drifted a bit off-topic.
 

Online Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6260
  • Country: fi
    • My home page and email address
Re: Pointer confusion in C -language
« Reply #76 on: June 26, 2021, 12:06:19 am »
Do you really think the essay length discussions of compiler details is of any help to the original poster who is still asking very basic questions?
When it shows step by step techniques on how to find out the answer for oneself, I do.

Have you noticed that my essay length answers do not just state things and tell you to trust me, but that the length is because I show why and how one can check?  Because I have zero faith in authority, and not much trust in beliefs, yours or mine.  You say Einstein, I say boo-hoo.  You say keep it short, I say hogwash: it is better to show how to find the answer than to just state the answer and hope they believe you.
 
The following users thanked this post: Siwastaja

Online Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6260
  • Country: fi
    • My home page and email address
Re: Pointer confusion in C -language
« Reply #77 on: June 26, 2021, 12:11:40 am »
1) "Every access (both read and write) made through an lvalue expression of volatile-qualified type is considered an observable side effect for the purpose of optimization and is evaluated strictly according to the rules of the abstract machine..."
That is exactly the sort of argument I want to avoid.

Put yourself in the developer shoes.  Tell me the situation where you want your code to do a three-instruction load-modify-write cycle instead of a single update instruction (especially which on AMD64, happens to be atomic too), when both work without additional external side effects (so no "well if you have a special memory-mapped register that" garbage; those need the inline assembly accessors anyway).  Every single case I can think of has something to do with trying to trick something or someone, and none have anything to do with trying to compute or achieve a result efficiently.  For the latter, the single update instruction always wins.

Just because the standard says something, does not mean its precise wording is the way it should be done.  Reality always wins over theory.  It does not matter how the C standard abstract machine works; what matters is whether the code a compiler generates is fit for practical purposes or not.

Consider the C standard the sales pitch for all C compilers.  If it delivers what it promises, then all is good.  If a compiler does not deliver, it better have a good reason for it.  But the sales pitch should never be how you measure things.
« Last Edit: June 26, 2021, 12:13:50 am by Nominal Animal »
 
The following users thanked this post: DiTBho

Offline brucehoult

  • Super Contributor
  • ***
  • Posts: 4036
  • Country: nz
Re: Pointer confusion in C -language
« Reply #78 on: June 26, 2021, 02:42:57 am »
a[ b ] being equivalent to b[ a ] is most often just a funny remark, I don't remember ever seeing actual use for this. After all, [] is eye candy making things more readable, and idx isn't readable. But I'm sure there's some obscure use for this I haven't seen.

You can use it to make your C code look more like assembly language.

Code: [Select]
char* silly_memcpy(char *a0, char *a1, char *a2){
  int a4; char *a5;

  if (!a2) goto L2;
  a2 = a1 + (int)a2;
  a5 = a0;

 L3:
  a4 = 0[a1];
  0[a5] = a4;
  a5 = a5 + 1;
  a1 = a1 + 1;
  if (a1 != a2) goto L3;

 L2:
  return a0;
}

This compiles to:

Code: [Select]
00000000 <silly_memcpy>:
   0:   ca19                    beqz    a2,16 <.L2>
   2:   962e                    add     a2,a2,a1
   4:   87aa                    mv      a5,a0

00000006 <.L3>:
   6:   0005c703                lbu     a4,0(a1)
   a:   00e78023                sb      a4,0(a5)
   e:   0785                    addi    a5,a5,1
  10:   0585                    addi    a1,a1,1
  12:   feb61ae3                bne     a2,a1,6 <.L3>

00000016 <.L2>:
  16:   8082                    ret

Sorry.
« Last Edit: June 26, 2021, 02:44:51 am by brucehoult »
 

Offline PlainName

  • Super Contributor
  • ***
  • Posts: 6844
  • Country: va
Re: Pointer confusion in C -language
« Reply #79 on: June 26, 2021, 11:19:14 am »
Quote
One reason runtime heuristics like stack canaries have such a bad time detecting this before the device has already crashed and pooped all over

Aren't such things meant to be debug/test tools, much like asserts, that highlight issues during development but aren't intended to be a cure for anything in production? In that context, a canary is useful after a crash because it tells you that some particular memory got corrupted. Sure, there may be several potential culprits, but you now know what you're looking for, and knowing it happened just after some particular operation is a massive clue. In production, like asserts, it's pretty useless since all you can really do is a reset rather than a halt.
 

Online Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6260
  • Country: fi
    • My home page and email address
Re: Pointer confusion in C -language
« Reply #80 on: June 26, 2021, 02:26:34 pm »
Aren't such things meant to be debug/test tools, much like asserts, that highlight issues during development but aren't intended to be a cure for anything in production?
What is "a cure for [anything] in production"?  I do not believe such a thing exists, just like I do not think security is something you can add on to a design.  You design it in in the first place; and if it is sick or needs a cure, you need a redesign.  Adding something on top to "cure" the design is a fallacy, and will fail.

In my view, in production, you either prevent a problem, or detect a problem; anything else is useless.

Heuristics like canaries are post-mortem indicators without false positives.  They can help pinpoint the mechanism of the problem (because if a canary is dead, showing positively a problem, then it for sure was involved), but as problem indicators they are of dubious utility (because a canary can be dead, but look perfectly alive).

The only reason I would use stack canaries in production, was if I would have access to core dumps from problem cases, or if I had nothing better available.

Ataradov's objection to hardware stack pointer tracking was that existing techniques like canaries or its extension, pre-filling the stack with a detectable pattern, is trivially implemented and sufficient for the use cases he can see.

I strongly disagreed, because what I want in production, is to catch and prevent the stack overflow in the first place.  (In later messages, after SiliconWizard pointed out both Ataradov and I were conflating buffer under/overruns with stack overflow – the first one being accesses outside of their intended target object, and the second one needing/using more stack than is available –, I pointed out that buffer overflows need support from the compiler, because the current C and C++ rules are such that they currently do not complain about obvious "this is very possibly outside the target object, and therefore a possible bug" code patterns that stick in my eye like a dirty thumb.)

Seeing that you seem to share how Ataradov sees the situation, forces me to think hard why that is.

To me, the situation in production is simple: A) Heuristics are useless for post-mortems because we don't get core dumps.  B) Heuristics can detect some problem situations, but not all; and the likelihood of a problem being detected is related to the computational effort spent.  This means that do make a robust thing, we must always balance computational efficiency and the statistical likelihood of detecting problems immediately when they occur: we cannot have both.

And that is the issue I have.  I do want both.  I need both efficient code, but also detect all problems that are possible to detect, reliably, when they do occur.

To anyone designing a commercial product, that is just one of the issues being balanced.  There, not having both is a practical limitation, and accepting it as such, and moving on to solving other problems, is perfectly acceptable, even clearly preferable attitude compared to mine.  If I was a CTO or product line head honcho, I would always hire Ataradov over myself.  And that is saying a lot, if you have any sort of a clue of how capable a developer I believe myself to be.

My own primary motivation on anything related to this stuff is to make sure that the stuff we have in the future is not just "better", but more robust than the shit we have now.

I know, for a fact, that robustness is something undesirable from business point of view; planned obsolesence is not malice, but simply an obviously working business strategy, one of the few ones that you can mathematically show from basic principles will work.
So, when you see me rail against long-term development efforts being directed using business rules, I am railing against choosing to race to the bottom-quality, maximum-profit products.  I see business (or more precisely, market competition) as one absolutely required part of a functioning society; but it too must balance/compete against humanitys own long term interests.  Thus, I do not object to business at all, just to using business as the yard stick here.

Elsewhere, I have described my own long term efforts, currently focusing at a replacement base library for systems programming in C (and as a variant, the base subset of functionality needed for the C/C++ freestanding environment used to develop low-resource embedded targets like microcontrollers).  It is slow going, exactly because it is not a business proposal but a research project.  It will not stop a developer using pointer expressions that can scribble over unrelated memory, because the C and C++ we have right now just does not even detect many of such patterns, and I do not expect the compiler developers to be interested in helping with that either; but if my shenanigans actually work, then the replacement base library might just induce developers to use patterns that avoid those problematic cases completely.

One surprisingly hard problem I'm chewing at is how to show that passing more information, often theoretically unneeded information, on the object being accessed is worth the extra "cost".

(This ought to be interesting to even beginners at C.)

Consider languages like Fortran and Python that support array slicing.  That is, instead of just passing a pointer to a consecutive sequence of elements, they can actually pass a subset or slice from an array.  The way it is used in high-performace computing in Fortran shows that while it does have overhead (more parameters passed per function call) shows that it is worth it, because code of average complexity doing this stuff tends to be more efficient if written in Fortran than when written in C.

At some point over a decade ago, I investigated this at the machine code level, read some computer science papers about efficient operations on 2D matrices, and discovered that passing the full description of how the matrix data is to be accessed, makes average complexity code more efficient and robust.

Here are the two structures used for double-precision floating-point data:
Code: [Select]
struct owner {
    long    refcount;
    size_t  size;  /* In doubles */
    double  data[];
};

struct matrix {
    struct owner *owner;
    int           rows, cols;
    long          rowstep, colstep;
    double       *origin;
};
Given struct matrix m, the expression used by the underlying code that implements the basic matrix operations to access the matrix element on row r, column c, is (m.origin + c*m.colstep + r*m.rowstep) , which evaluates to a pointer to a double.  At runtime, r >= 0, c >= 0, r < m.rows, c < m.cols.  As an optional consistency check,the pointers m.origin, (m.origin+(m.rows-1)*m.rowstep), (m.origin+(m.cols-1)*m.colstep), and (m.origin+(m.rows-1)*m.rowstep+(m.cols-1)*m.colstep) all must be at or above m.owner->data and below m.owner->data+m.owner->data.size.

You might think that that means the code has to do two multiplications per matrix element access, but that's not true in practice.  You see, to optimize the efficiency of matrix operations, and to leverage single-instruction-multiple-data instructions available on many architectures, depending on the exact operation, we'll want to rearrange the data anyway.  You see, for all but the smallest fixed-size matrices, the access order and data locality determines how long the operation takes.  The arithmetic operations themselves (addition, subtraction, and multiplication in particular) are not the bottleneck; the time needed to access the elements is.
(This also makes this annoying to benchmark.  If you use a synthetic microbenchmark, you are reserving the entire cache for this operation only.  But, in real life code, the operation is only a part, a single step, in some chain of operations; and overusing cache at one step often means another step has to pay a much higher price for memory access.  So, optimizing the heck out of one operation, can easily lead to real world code that performs worse.)

The true strength those structures bring to the programming table, is that now you can have a matrix and its transpose refer to the exact same data.  You can even have a vector corresponding to its diagonal elements.  Modifying an element in one, modifies the elements in all others, because they refer to the same data in memory.  None of them are "secondary" or "views"; each is just as primary as every other matrix referring to the same data.  The refcount is optional, and normally records the number of uses of the referred to data, including matrices and temporary uses in elementary calculations.  (That is, when a function does say a matrix-matrix multiplication, it starts by incrementing the refcounts of the two owners of the data.  After the operation is completed, the refcounts are decremented.)

As an example for single-dimensional arrays, C still does not have a standard function or a really efficient way to repeat a byte pattern at the beginning of an array to the rest of the array –– even though that is exactly what the unused branch of every single memmove() implementation would do!
 

Offline PlainName

  • Super Contributor
  • ***
  • Posts: 6844
  • Country: va
Re: Pointer confusion in C -language
« Reply #81 on: June 26, 2021, 02:54:42 pm »
A canary is most useful when the program doesn't crash. It may appear to run fine and you release to production, whereupon it goes wrong. Or it seems to run fine until something strange happens, the cause of which may have occurred 20 minutes previously so you have no idea how or why it went wrong. The canary will tell you it has just done something bad, and then preferably halt the system since continuing wouldn't be useful (and also because if you have a debugger attached you'll drop into that).

As you note, detecting a problem like this in production is only useful in that you can do a reset. What you're trying to do is not have that problem in the first place, but without something like a canary you may never know one is lurking. That's why I suggest that canaries, like asserts, are entirely a test tool and not a production save-our-ass thing. Core dump or not, production isn't the place to have to deal with it.

Hardware-based protection in production code is a different matter. AFAICS, its use is not to make things recoverable (other than by restarting) but to prevent further Bad Things from happening. It shouldn't be needed, but shit happens and it will make sure the splashes stay in the bowl rather than propagate to the floor and walls, in a manner of speaking.
 

Offline DiTBho

  • Super Contributor
  • ***
  • Posts: 3915
  • Country: gb
Re: Pointer confusion in C -language
« Reply #82 on: June 26, 2021, 04:00:26 pm »
shit happens and it will make sure the splashes stay in the bowl rather than propagate to the floor and walls, in a manner of speaking.

This is exactly the philosophy behind the carbon fans used in Arctic airplanes. They can break at some point, but are designed to keep all splinters segregated inside a cage rather than letting them propagate  :D
The opposite of courage is not cowardice, it is conformity. Even a dead fish can go with the flow
 

Offline DiTBho

  • Super Contributor
  • ***
  • Posts: 3915
  • Country: gb
Re: Pointer confusion in C -language
« Reply #83 on: June 26, 2021, 04:11:33 pm »
Aren't such things meant to be debug/test tools, much like asserts, that highlight issues during development but aren't intended to be a cure for anything in production? In that context, a canary is useful after a crash because it tells you that some particular memory got corrupted.

That was a great help when I debugged a PCI-board on an RISC workstation. The hardware comes with a special NVRAM assisted by the firmware where the last PCI-transactions are logged; if something crashes the Linux kernel, you can read the logs and have some good clue.

In my case, there was a bug with the PCI-sATA chip, thanks to this trick I catched and fixed it  :D
The opposite of courage is not cowardice, it is conformity. Even a dead fish can go with the flow
 

Online Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6260
  • Country: fi
    • My home page and email address
Re: Pointer confusion in C -language
« Reply #84 on: June 26, 2021, 08:55:29 pm »
Hardware-based protection in production code is a different matter. AFAICS, its use is not to make things recoverable (other than by restarting) but to prevent further Bad Things from happening. It shouldn't be needed, but shit happens and it will make sure the splashes stay in the bowl rather than propagate to the floor and walls, in a manner of speaking.
Emphatic yes, and a tentative no.  What you describe is the minimum we know is achievable; so definitely yes.  What we really do not know, is what real-world patterns would work with regards to say stack overflow (not due to any programming errors, just needing more stack than is available).

Here is that practical example of mine, more fleshed out this time.

Let's assume we have hardware with three internal registers not directly supported by the instruction set, but available (albeit perhaps a bit "slow") to interrupt code.  Two of them define a minimum and maximum address to be used with stack pointer relative addressing, and instructions that manipulate the stack pointer.  The hardware compares the effective address (of the stack pointer relative address), and compares to the internal registers.  If outside, the effective address is loaded to the third register, and a hardware interrupt is fired.

Let's say we are using freestanding C/C++ similar to many microcontroller development environments currently in use, without any specific compiler modifications or additions.

For the hardware interrupt, we implement a function that keeps two addresses somewhere in RAM not likely to be clobbered by stack or heap operations.  One address is used to reset the stack pointer, and the other address is used to pass control to after the interrupt returns.  (Since this kind of interrupt cannot use RAM, there likely is a fourth register containing the address to jump to to "return" from the interrupt, so that the body of the interrupt function would simply set the stack pointer and that register to the stored values.)

By default, the runtime environment (bootloader) would set the stack reset address to beginning of RAM, and the jump target to a hardware reset routine.  This means that without any other changes, if the stack grows out of bounds, the hardware gets reset.  No heuristics, guaranteed correct operation.  (However, this will not be able to catch second-hand buffer overflow issues, ie. when other code accesses beyond an object that is stored on stack, because such accesses, as Ataradov showed, do not use stack pointer relative addressing.  The basic pattern is passing the address of the object on stack to a function, and that function doing an access beyond the object it is given.)

However, now consider a microcontroller firmware interfacing to some data acquisition circuitry or sensors, using say a SCPI compatible protocol.  (Processing such protocol messages is where I typically see stack use exceeding the available.)

At the beginning of processing an incoming control message, the firmware stores the current stack overflow address pair, and sets them to a specific instruction in the same C scope, and the current stack pointer.  Then, it starts parsing the control message data.  After the message has been parsed, the original stack overflow address pair is restored, and execution continues as normal.

Here is where the Useful Magic could happen.

Let's say that because of available stack space limits, the function call chain trying to parse the message runs out of available stack space, and triggers the stack overflow interrupt.  (Here, we assume that the interrupt occurs after the calculation of effective address, but before the access to that address is performed, so nothing unrecoverable like stack spilling over to non-stack RAM has yet happened.)

If the call chain has done no changes to globally observed state –– and this being a message parsing call chain, there either are no such changes, or the changes can be safely ignored –– then this mechanism reverts the essential machine state back to the one at beginning of the parsing, so the code can simply discard the message and respond with "Sorry; I'm so low on stack, I cannot process that message right now."

See?  This is the second pattern I personally would use with such hardware.  The first would be that hard restart on stack overflow.  Third would be to use it for dynamic instrumentation, where the effective address just pushes the limit onwards; with some other code regularly checking the stack pointer against the limit, and shrinking the limit if it looks to be "too far".  This does not give optimum granularity, but during development, especially with purpose-built nasty testcases, it would be very informative.  I'd love to have this too.

And, to repeat, this pattern is not unprecedented in C at all: setjmp()/longjmp() provides basically the same "revert to earlier state" functionality in bog-standard C.
The pair does have a bad reputation, because it is one of the footguns that seems to have maimed more feet than the bugs it has prevented.  The most widely known one is probably the privilege escalation in ftp servers by simply interrupting the server side at just the right moment.

I don't know if there are other practical patterns one could use with such hardware stack pointer related effective address checking, but for embedded targets alone, the above two use cases makes it –– in my opinion –– obviously desirable on any and all microcontroller architectures.

Then again, I am not an EE nor have I designed even the simplest core on an FPGA yet, so I don't really know how much effort and resources such effective-address checking and interrupt facilities would require.  I personally would happily accept say an additional clock cycle use for stack pointer relative addressing, if necessary for the support; I see the above two use cases so darned useful in practice, if one wants to make more robust embedded devices of this described type.  And I want the more robust embedded devices, because we already have enough cheap shit.
 

Offline DiTBho

  • Super Contributor
  • ***
  • Posts: 3915
  • Country: gb
Re: Pointer confusion in C -language
« Reply #85 on: June 26, 2021, 11:10:14 pm »
And I want the more robust embedded devices, because we already have enough cheap shit.

That's the problem: cheap! These tools already exist for mission critical CPUs, e.g. Lauterbach TRACE32 costs a lot of money, but offers a couple of power ICE and simulators which can check everything everywhere run-time. It can also be used for dynamic coverage, profiling ... etc

The opposite of courage is not cowardice, it is conformity. Even a dead fish can go with the flow
 

Offline DiTBho

  • Super Contributor
  • ***
  • Posts: 3915
  • Country: gb
Re: Pointer confusion in C -language
« Reply #86 on: June 26, 2021, 11:42:35 pm »
I don't really know how much effort and resources such effective-address checking and interrupt facilities would require.

Talking about "poor man" solutions, the previously mentioned 68hc11 trick comes from a companion chip used by a company affiliated with Motorola back in the 90s.

Unfortunately this company is no more in business and I wasn't able to contact any retired engineer. Back in 1989, they made a super powerful and relatively not too expensive ICE. I found one "as-is" on eBay, I don't have the software, except a demo program that shows how the ICE can be useful for checking SP. I started reverse engineering the board to find out how it works, only partially understood something, but only found there is a companion chip monitoring the CPU at the bus level and I guess it's able to decode each instruction that uses the stack.

Code: [Select]
(68hc11) is_stack_ok = (address_on_the_bus >= SP_begin) and (address_on_the_bus < SP_end) and is_it_a_stack_operation;

is_it_a_stack_operation = fetch_and_decode the data_bus of the CPU
         for certain op-codes like { push, pop, function_call, ...} the answer is 1, otherwise 0 

There are several modules on the ICE, I don't know what they do and how they work, but in particular the companion chip is really a stand-alone hardware "stack boundary checker". Looking at what goes on the bus from the 68hc11, I found it's memory mapped device provided with two 16 bit registers to set SP_begin and addr < SP_end, plus a register to "enable/disable" a pin that is set high when SP is not within its programmed range.
Code: [Select]
0x0800 SP_begin
0x0802 SP_end
0x0804 control
(how the ICE11's companion chip maps its registers)
You can connect its output pin with whatever you want. To a flip-flop with a LED, or to an interrupt pin, or why not to both?

The companion chip can be memory mapped and programmed, so in my setup, I have a 68HC11EVB in expanded mode (the CPU directly addresses its RAM, ROM, and peripherals), the companion chip is memory mapped into the "expansion area" of the 68hc11 like if was a ACIA device (uart); I can load programs from the ACIA0 to programs the boundaries, I can load other programs and run them step-by-step. That's how I discovered how it reacts when it's programmed by the demo application.

----- Can it work with a softcore? -----

I don't know how the ICE11 works internally, I only use it for my purpose and I have only added a latch and a LED, but I can reproduce its behavior for a MIPS32 Softcore.

How?

Code: [Select]
(mips32) is_stack_ok = (EA >= SP_begin) and (EA < SP_end) and (is_SP_involved_with_EA);

is_SP_involved_with_EA = ((ireg(reg) and mask) not_equal_to zero)

On a simple RISC (like MIPS and RISC-v), the load/store stage always comes in the form (reg + offset),
which always ends with a simple EA register, EA = reg + offset,  that is then directly passed to the MAR (memory address register,) along the CPU data-path

MAR = EA = reg + offset            That's good for the trick!

You set a mask, say register { 29 30 }.
Code: [Select]
0          1          2          3
01234556789012345567890123455678901
00000000000000000000000000000000110
Say you use "reg 30" as stack-pointer and "reg 29" for a push operator; here is how crazy Gcc goes with that stuff, other compilers always use SP + offset, while Gcc first loads SP into another register, does some math with it, then it uses it in load/store, and moral of the story, you have to check from time to time which registry is involved in the operations.

Anyway, supposing you somehow know it's "reg 29"

The ireg(reg) circuit will output
Code: [Select]
00000000000000000000000000000000100

The circuit "is_SP_involved_with_EA":
Code: [Select]
ireg 00000000000000000000000000000000100 <---- in0 which reg is currently used during load/store?
mask 00000000000000000000000000000000110 <---- in1 which reg/regs are supposed to be SP-related?
and  00000000000000000000000000000000100
....
out  00000000000000000000000000000000001 <---- out is the load/store is actually a stack-operation?


Bingo! This stuff doesn't cost many resources, it just costs "something" to properly update the mask *before the stack operation* (good news, it could be integrated with the machine-layer of Gcc), plus a couple of stall-cycles in the load/store unit (only in a pipe-lined design), and you have a circuit that checks if the effective address of a load/store is within its assigned range.


On MIPS, this stuff can be implemented
1) as Coprocessor, and programmed by using special cop-instructions
2) as memory mapped device, similar to the ICE11's companion chip
« Last Edit: June 27, 2021, 12:08:33 am by DiTBho »
The opposite of courage is not cowardice, it is conformity. Even a dead fish can go with the flow
 

Offline PlainName

  • Super Contributor
  • ***
  • Posts: 6844
  • Country: va
Re: Pointer confusion in C -language
« Reply #87 on: June 27, 2021, 12:03:23 am »
Quote
Here is where the Useful Magic could happen.

That's an interesting thought experiment, but I don't think you need hardware for that. Typically, stack grows one way and heap grow the other, but it's usually possible to know where the limit of the stack is. All you need is a quick check of where the stack pointer is pointing vs the known end of the stack, and you can figure out if you don't have room for another call.

Admittedly, hardware would be quicker, but then you're having to clean up after the fact rather than just not get in the mess in the first place.

Looked at another way, if it were heap you'd know if you've got a problem because malloc() will say so. What you're suggesting is, it seems to me, malloc() always returning a pointer but when you try to access the memory you get an access interrupt or exception. So in that context that the stack thing is like you'd say to malloc() "Hey, got an extra 10K I can have?" and it replies OK or not, then you can grab it or.. well, however you want to fail.

Maybe :)

Quote
And I want the more robust embedded devices, because we already have enough cheap shit.

Well, as DiTBho says, you can apparently have all that if you pay the price. The thing is that cheap shit is why everyone has at least one mobile phone, IoT are ten a penny, etc.
 
The following users thanked this post: DiTBho

Online Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6260
  • Country: fi
    • My home page and email address
Re: Pointer confusion in C -language
« Reply #88 on: June 27, 2021, 12:17:59 am »
And I want the more robust embedded devices, because we already have enough cheap shit.
That's the problem: cheap! These tools already exist for mission critical CPUs, e.g. Lauterbach TRACE32 costs a lot of money, but offers a couple of power ICE and simulators which can check everything everywhere run-time. It can also be used for dynamic coverage, profiling ... etc
That's the business way to go about it, yes.  You make a product you can charge through the nose for, even though the same features could be had for cheaper if anyone cared.  Anyone does not care, because cheap == unreliable; you must spend money to be believable in the business world.

That is also the reason I myself am not trying to design a new programming language and a new compiler that would beat the competitors off the waters.  Because to try and do that, is to forget the human aspect: you never know beforehand what those clever monkeys get up to.  So, instead of trying to force them into a pre-designed mold, I want to see small incremental steps, an evolutionary pressure if you will, towards more robust devices.  I don't want everyone to switch to expensive Enterprise-quality Mission Critical Devices with fifteen Certificates of Quality from ten different agencies, because that is just as shitty but in a different way.

Talking about "poor man" solutions
The true "poor man" solution would be to modify the compiler to emit machine code doing the checks explicitly, and emulate the interrupt whenever necessary.  It costs zero hardware.

it's usually possible to know where the limit of the stack is
You still do not seem to be able to differentiate between "heuristic" and "deterministic", it seems.
 

Offline hamster_nz

  • Super Contributor
  • ***
  • Posts: 2803
  • Country: nz
Re: Pointer confusion in C -language
« Reply #89 on: June 27, 2021, 03:08:39 am »
Just throwing this on the fire...

C support for variable length arrays.

WORST MOVE EVER.
Gaze not into the abyss, lest you become recognized as an abyss domain expert, and they expect you keep gazing into the damn thing.
 
The following users thanked this post: DiTBho

Offline brucehoult

  • Super Contributor
  • ***
  • Posts: 4036
  • Country: nz
Re: Pointer confusion in C -language
« Reply #90 on: June 27, 2021, 04:31:59 am »
But C doesn't support arrays at all, whether fixed or variable length.
 

Offline hamster_nz

  • Super Contributor
  • ***
  • Posts: 2803
  • Country: nz
Re: Pointer confusion in C -language
« Reply #91 on: June 27, 2021, 04:45:09 am »
But C doesn't support arrays at all, whether fixed or variable length.

Code: [Select]
#include <stdio.h>
#include <string.h>

void puts_both(char *a, char *b) {
   char c[strlen(a)+strlen(b)+2];  // Urgh! What where they thinking!
   strcpy(c,a);
   strcat(c," ");
   strcat(c,b);
   puts(c);
}

int main(int argc, char *argv[]) {
   puts_both("Hello","World");
   return 0;
}

Builds fine without errors.

Code: [Select]
hamster@hamster-acer5:~/vla$ make
gcc -o vla main.c -Wall -pedantic
hamster@hamster-acer5:~/vla$ ./vla
Hello World
hamster@hamster-acer5:~/vla$
Gaze not into the abyss, lest you become recognized as an abyss domain expert, and they expect you keep gazing into the damn thing.
 

Offline brucehoult

  • Super Contributor
  • ***
  • Posts: 4036
  • Country: nz
Re: Pointer confusion in C -language
« Reply #92 on: June 27, 2021, 06:28:25 am »
That's not C until c99. Using --std=c89 -pedantic will cause it to be rejected. I had a feeling it's allowed in c++14 but gcc rejects it in all cases with -pedantic. gcc/g++ accept it in all cases without -pedantic.

But I think you miss my point. That's not declaring what I or several others here would call an "array". It's merely allocating space and giving you a pointer to the start of it. If you forgot the +2 or made a mistake and put +1 then you would have potential catastrophe, with no warning.
 

Offline hamster_nz

  • Super Contributor
  • ***
  • Posts: 2803
  • Country: nz
Re: Pointer confusion in C -language
« Reply #93 on: June 27, 2021, 07:07:38 am »
Nevertheless that is what this stack-shagging feature is called:

https://c-for-dummies.com/blog/?p=3488

Quote
The C99 standard added a feature to dimension an array on-the-fly. The official term is variable length array or VLA. And while you might think everyone would love it, they don’t.

It also had that problematic issues that people who use it can't see the issue with using it... "But you are allocating what could be huge blocks of memory on the stack at runtime!" usually gets the response of either  a look of confusion, or "I know! It's neat eh?"
Gaze not into the abyss, lest you become recognized as an abyss domain expert, and they expect you keep gazing into the damn thing.
 

Offline DiTBho

  • Super Contributor
  • ***
  • Posts: 3915
  • Country: gb
Re: Pointer confusion in C -language
« Reply #94 on: June 27, 2021, 08:36:51 am »
That's the business way to go about it, yes.  You make a product you can charge through the nose for, even though the same features could be had for cheaper if anyone cared.

I did this mistake years ago with an electric kart ... I had said "I don't see what could go wrong, let's do it with the the cheapest wrenches", then I lost one wheel in a corner ...  and I crashed into a hay bale. Now I know, why pneumatic ratchet wrenches are better.

Oh, but talking about things that could go wrong ... have you ever used a gasoline arctic camping stove? I made the two big mistakes of underestimating the idea of paying for some good advice, and bought the cheapest rubber seals.

Inspected, they looked good ... just ... I then found out that below -20C they break. At -45 ° C, it means no fire, no liquid water to drink, no food.

Arctic rubber seals cost much much more the cheapest rubber seals, but they are worth each penny they cost  :D
« Last Edit: June 27, 2021, 09:29:53 am by DiTBho »
The opposite of courage is not cowardice, it is conformity. Even a dead fish can go with the flow
 

Offline DiTBho

  • Super Contributor
  • ***
  • Posts: 3915
  • Country: gb
Re: Pointer confusion in C -language
« Reply #95 on: June 27, 2021, 09:15:57 am »
The true "poor man" solution would be to modify the compiler to emit machine code doing the checks explicitly, and emulate the interrupt whenever necessary.  It costs zero hardware.

Yeah, I have massively used when I was a student and seen in paid job several times.

I remember a couple of paid consultancies, they were working on a Engine control unit based on Motorola 332, and I used a very powerful Avoget toolsuite to instrument the code and check everything in software(1), but then they bought me a professional BDM-base ICE to complete the job.

(1) the m68k architecture facilitates this task.
The opposite of courage is not cowardice, it is conformity. Even a dead fish can go with the flow
 
The following users thanked this post: Nominal Animal

Offline DiTBho

  • Super Contributor
  • ***
  • Posts: 3915
  • Country: gb
Re: Pointer confusion in C -language
« Reply #96 on: June 27, 2021, 09:27:25 am »
Just throwing this on the fire...
C support for variable length arrays.
WORST MOVE EVER.

Languages like eRlang don't even allow you to reassign a value to a variable, if you want to do it, you have destroy the variable and recreate it, which looks extremely restrictive but it has its purpose, while that stuff in C is ... I do find it very stupid because too prone to fail and very hard to be debugged, even with a smart ICE, you should instrument the ICE to check it.

I agree with you: worst move ever!
The opposite of courage is not cowardice, it is conformity. Even a dead fish can go with the flow
 

Offline brucehoult

  • Super Contributor
  • ***
  • Posts: 4036
  • Country: nz
Re: Pointer confusion in C -language
« Reply #97 on: June 27, 2021, 11:04:46 am »
Nevertheless that is what this stack-shagging feature is called:

Sure. Because C "arrays" in general are not arrays, but only allocating space and pointing at it.

Quote
It also had that problematic issues that people who use it can't see the issue with using it... "But you are allocating what could be huge blocks of memory on the stack at runtime!" usually gets the response of either  a look of confusion, or "I know! It's neat eh?"

It's fine if you're on a machine with GB of stack space in VM. Or if you do some sanity check on the size.

Linus complains that it generates less efficient code than a statically-sized array. True, but it's a lot more efficient than malloc() at the start of the function and free() at the end. You can't always know a sensible maximum size to pick for that statically sized array, especially in library code where you don't know how big a stack the machine you're running on will have -- a few KB may seem sensible until someone runs your code on a machine with hundreds of MB of stack and passes in big data. Or until they run your code on an AVR.

I think I can safely say that I have never written a dynamically sized array in C. I do however use alloca() sometimes, which amounts to the same thing.

Once you get to a certain size, the execution time to actually do something with the array will be enough more than the execution time of malloc() and free() that you don't care and it may as well be on the heap. If your malloc package is sensible and doesn't fragment things all to hell with large malloc()/free() repeated millions of times.
 

Offline PlainName

  • Super Contributor
  • ***
  • Posts: 6844
  • Country: va
Re: Pointer confusion in C -language
« Reply #98 on: June 27, 2021, 11:16:41 am »
Quote
but it's a lot more efficient than malloc() at the start of the function and free() at the end.

Efficiency counts for nowt if you run out of stack. With malloc() at least you can see it failed before just running off the end. I'm surprised Linus went for efficiency over robustness with his argument.

Code: [Select]
char c[strlen(a)+strlen(b)+2];  // Urgh! What where they thinking!
ummm... shouldn't that be +1? I mean, 2 does no harm but only 1 is necessary. Unless I haven't yet had enough coffee this morning and missed something.
 

Offline hamster_nz

  • Super Contributor
  • ***
  • Posts: 2803
  • Country: nz
Re: Pointer confusion in C -language
« Reply #99 on: June 27, 2021, 11:23:20 am »
Code: [Select]
char c[strlen(a)+strlen(b)+2];  // Urgh! What where they thinking!
ummm... shouldn't that be +1? I mean, 2 does no harm but only 1 is necessary. Unless I haven't yet had enough coffee this morning and missed something.

I wanted room for:

* the first word
* a space
* the second word
* the terminating null character.

So it seems right to me...

Or maybe more explicitly:

Code: [Select]
char c[strlen(a)+1+strlen(b)+1];  // Urgh! What where they thinking!
Gaze not into the abyss, lest you become recognized as an abyss domain expert, and they expect you keep gazing into the damn thing.
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf