rather than say things like
why not try to explain why the code works with respect to the standard.
rather than say things like
why not try to explain why the code works with respect to the standard.
I'm not sure what you mean?
See Denying the antecedent - Wikipedia,
If your code is valid, following all rules of the standard, then the standard guarantees that the result of that code will be correct as well.
If your code is not valid, the standard doesn't guarantee anything. One of the possible outcomes is that it produces a result that seems sensible to the programmer. Or it could crash or return a completely bogus result.
I think the point is that it may work, but an unrelated change in the code may cause it to fail, particularly if the optimizer sees something new that it can take advantage of. Especially in small examples, you'll likely get away with invalid casts ... until you don't
i believe using pointers to predictably access absolute hardware addresses and for other purposes is the bedrock of C and is what makes C a systems language allowing it to be used to build an OS and other embedded applications.
I'm sorry to have to be this blunt, but then your belief is false.
you think any language could be use to build a OS?
No, my point is that C doesn't allow you to predictably access pointers in the way you've described, regardless of any beliefs of its users.
so you don't think being able to predictably access absolute hardware addresses is a requirement for the language used to write an OS or an embedded application?
i'm not convinced you clearly understand what i've been trying to say, understand the limits of what i've said
That's not what I said at all.
✓ Predictably accessing absolute hardware addresses is a requirement for a language used to write an OS or an embedded application.
✗ C allows you to use pointers to predictably access absolute hardware addresses and for other purposes.
You cannot use pointers in C as if they were addresses in assembly.
so your saying it's a requirement for coding an OS but C doesn't meet that requirement?
C allows you to access absolute hardware addresses because implementations define the conversion from an integer to a pointer in a sensible way.
C does not allow you to predictably access data using invalid pointers “for other purposes”.
I believe the former is enough for OS kernels and other embedded applications*. But the former does not imply nor require the latter.
(*) Assuming access to the necessary intrinsics or inline assembly for some of the required instructions.
you know C was developed as Unix was being developed specifically to replaced the use of assembly in the OS. C was written specifically for the purpose of writing a high performance OS which became portable because of C.
my contention has been to use the pointers validly, without relying on the compiler for protection and are similarly used in the Unix kernel for process management and drivers as i've described, in other words used to operate on internal data.
i don't know how the standard can describe such protection and don't expect it to. but it appears that over the 4 decades of use, pointers behaved consistently as i've described and work reliably when used intelligently.
maybe i'm not understanding you correctly. in post #82 i showed code that cast a byte pointer to both a long and float and accessed them as bytes and printed the long and float values as bytes, as expected.
i used that ptr to access the 4 bytes of the long and 4 bytes of the float. there's nothing to prevent me from reading 13 bytes starting at the address of the long.
my understanding is that you believe this is an improper (not guaranteed) use of the pointer because it points to something that is not of its type -- the byte ptr is to a float (the ptr is the adderss of the float) and therefore it's use is invalid? is this correct in a nutshell?
obviously our mindsets, and very likely the types of software we've written are different.
i agree your concerns are relevant when the code needs to run on different platforms (e.g. MAC, Intel, Motorola, ARM, ...) and using different compilers or versions of compilers. your software doesn't likely depend on hardware and must be hardware agnostic
most of my experience is making hardware work (data, speakerphone, optical, radio, ethernet). my code isn't going to run on different hardware. and most cases is memory or processing time constrained.
so by definition, the code i've worked doesn't need to be as portable.
the ptr issues in my case have been whether the memory the ptr pts to is still allocated.
Real-world operating systems are full of assembly: https://github.com/torvalds/linux/search?l=Unix+Assembly
But let's not get into that right now.
Sure, but some of the uses you've proposed in this thread are not valid according to the C standard, which is an issue.
What is your definition of “validly”?
The compiler doesn't provide protection. Rather, the compiler is what's interpreting your C code, not the hardware. If you provide it with invalid code, it may produce invalid results.
Your casts don't need to be valid to enjoy any protections from the compiler, the casts need to be valid to make sure that the compiler generates working code.
Perhaps there's a misunderstanding about what the compiler actually does:
The compiler does not literally translate C or C++ into assembly or machine code. Rather, the compiler interprets your C code and generates a binary that produces the same results as if your C code were interpreted by the C abstract machine defined by the standard.
If you don't follow the rules that the standard sets out for the abstract machine, your code is wrong, and the compiler will generate a binary that doesn't do what you expect.
In that case, even if a literal translation of your C code to x86 assembly or machine code would give the correct result, the binary generated by the compiler might not, because the x86 hardware is different from the C abstract machine.
From the way you phrased things in your posts, it seems that perhaps you think that if you write correct code, then you get a correct binary with some additional checks by the compiler, and that if you write code with invalid pointer casts, you still get valid code, but without checks by the compiler.
This is not the case. If you write code with invalid pointer casts, the compiler may actively break your code, and the behavior will then no longer match what you expected.
Ignoring one major detail, yes. But I don't want to confuse matters even further, so put a pin in that for now.
This isn't an issue of portability, but of correctness. Even on the same hardware and using the same compiler etc. you may run into issues. If you edit even a single innocuous line of code, this can completely change the generated code, because the compiler might find new opportunities for optimization, as mentioned by wildbill.
Compilers have all kinds of heuristics and internal thresholds. For example: what's the maximum number of instructions in a function to make it eligible for inlining, at what point does it unroll a loop, etc.
Inlining a function can cause a chain reaction, because it reveals more opportunities for optimization, so the generated code may change drastically.
If this causes your invalid pointer operation to be optimized differently, you may suddenly get a different result.
Packed is one such compiler extension (gcc here) that @PieterP was referring to. By doing so you add new constraints to the compiler and limit optimization it might chose to do otherwise. This helps getting your mental model closer to what the compiler can do, but still (as mentioned and proposed in the revision of that code ) a memcpy() would help stay within the bounds of defined behavior.
Regarding direct memory access: Assigning an int to a pointer would be caught by the -Werror flag and The cast of the int to a volatile pointer will be accepted and you’ll get the expected behavior because again of the relaxed optimization constraints associated with volatile
- volatile object - an object whose type is volatile-qualified, or a subobject of a volatile object, or a mutable subobject of a const-volatile object. Every access (read or write operation, member function call, etc.) made through a glvalue expression of volatile-qualified type is treated as a visible side-effect for the purposes of optimization (that is, within a single thread of execution, volatile accesses cannot be optimized out or reordered with another visible side effect that is sequenced-before or sequenced-after the volatile access. This makes volatile objects suitable for communication with a signal handler, but not with another thread of execution, see std::memory_order). Any attempt to refer to a volatile object through a glvalue of non-volatile type (e.g. through a reference or pointer to non-volatile type) results in undefined behavior.
Also, see the reference documentation for memcpy (the last point in the note)
Notes
std::memcpy
may be used to implicitly create objects in the destination buffer.
std::memcpy
is meant to be the fastest library routine for memory-to-memory copy. It is usually more efficient than std::strcpy, which must scan the data it copies or std::memmove, which must take precautions to handle overlapping inputs.
Several C++ compilers transform suitable memory-copying loops tostd::memcpy
calls.
Where strict aliasing prohibits examining the same memory as values of two different types,std::memcpy
may be used to convert the values.
i don't know what else can be said.
i've spent a career using ptrs in a way you say is not valid, will not generate correct code.
Do you not believe that these pointer casts are in fact invalid?
If that's the case, I encourage you to actually read the language reference. This is well documented, it's not something I came up with to annoy you.
If you do not believe that undefined behavior is an issue, have a look at the examples in https://en.cppreference.com/w/cpp/language/ub#UB_and_optimization and https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#es48-avoid-casts.
not sure what invalid means to you. (which specific examples are you referencing)
ptr values that make sense to me when the code is written intelligently. there are many caveats.
re: volatile
from K&R 2nd ed, pg 196 -- "declaring it volatile announces that it has special properties relevant to optimization".
my understanding is "volatile" tells the optimizer not to assume the value is the same as the last time it may have been read if that value is still in a register. if it only affects the optimizer, what affect does it have on code generation.,
the following compiles without warning using gcc and is typical of what i've done. i don't believe there is anything special about either pointer in terms of code generation except for the impact on the optimizer not to optimize out a repeated read/write
int main() {
int *p = (int*) 0x1234;
volatile int *q = (int*) 0x1234;
printf (" p %p, q %p\n", p, q);
return 0;
}
output from above
p 0x1234, q 0x1234
a trivial example from Arduino code. "reg" is defined as a volatile ptr set to an address and "*reg" used to access a memory mapped hardware register
while it should be defined as volatile for the optimizer, i don't believe it's usage as a ptr is any different from an int*
#define digitalPinToPort(P) ( pgm_read_byte( digital_pin_to_port_PGM + (P) ) )
void pinMode(uint8_t pin, uint8_t mode)
{
uint8_t bit = digitalPinToBitMask(pin);
uint8_t port = digitalPinToPort(pin);
volatile uint8_t *reg, *out;
if (port == NOT_A_PIN) return;
// JWS: can I let the optimizer do this?
reg = portModeRegister(port);
out = portOutputRegister(port);
if (mode == INPUT) {
uint8_t oldSREG = SREG;
cli();
*reg &= ~bit;
*out &= ~bit;
i think this differences in these 2 snippets of code (post #45) highlight the issue. if memcpy doesn't actually copy anything, why does it matter? does it do anything to guarantee that *buf pts to a block of memory that has a properly formatted, properly aligned msg? that may just happen to have a small integer that looks like an ID but is a corrupted msg?
By invalid casts I mean any cast that is not in the list of allowed casts.
For example:
struct S { ... };
unsigned char buffer[1024];
fill_buffer_with_some_data(buffer, sizeof(buffer));
S *ps = (S *)buffer; // invalid cast
The optimizer produces the intermediate representation that is then passed to the code generator. If it affects the optimizer, it will have an effect on the generated code.
Because the standard says “using memcpy is allowed, using a pointer cast is not allowed”. What other reason do you need?
No.
i believe your saying it's an invalid cast because buffer is not the same type as S. why would i need a cast if the rhs side is the same type as the llhs?
what if the data in buffer is a properly formatted and aligned struct S?
but the optimizer doesn't generate the "logic" the src code describes. the optimizer is not going to flag a syntax error
can you provide the reference?
[this stackoverflow response] makes a ton of sense to me, some of the caveats i mentioned before.
(memory - Why use memcpy() when you can directly pass pointers to function in C? - Stack Overflow) provides valid reasons when a cast shouldn't be used: 1) casting to a larger size variable and 2) improperly byte aligned value of said type
i can understand that it may be good practice but unnecessary when efficiency is critical
of course it's undefined what happens when the compiled code doesn't generate the data. the developer needs to validate these things and take responsibility for handling corrupt data.