Hi everybody,
I am back to programming in C after many years:
indeed I have forgotten so many things, including
how much I love this language. :)
In particular, I am using C90, and compiling with
`gcc ... -ansi -pedantic -Wall -Wextra` (as I have
the requirement to ideally support any device).
To the question, I was reading this, but I am not
sure what the quoted passage means:
Matt Stancliff, "So You Think You Can Const?",
<https://matt.sh/sytycc>
<< Your compiler, at its discretion, may also choose
to place any const declarations in read-only storage,
so if you attempt to hack around the const blocks,
you could get undefined behavior. >>
I do not understand if just declaring that a pointer
is to constant data may incur in that problem even
if the pointed data was in fact allocated with malloc.
I would say of course not, but I am not sure.
E.g. consider this little internal helper of mine
(which implements an interface that is public to
do an internal thing...), where I am casting to
pointer to non-constant data in order to free the
pointed data (i.e. without warning):
```c
static int MyStruct_free_(MyStruct_t const *pT) {
assert(pT);
free((MyStruct_t *)pT);
Assuming, as said, that the data was originally
allocated with malloc, is that code safe or
something can go wrong even in that case?
Hi everybody,
I am back to programming in C after many years:
indeed I have forgotten so many things, including
how much I love this language. :)
In particular, I am using C90, and compiling with
`gcc ... -ansi -pedantic -Wall -Wextra` (as I have
the requirement to ideally support any device).
To the question, I was reading this, but I am not
sure what the quoted passage means:
Matt Stancliff, "So You Think You Can Const?",
<https://matt.sh/sytycc>
<< Your compiler, at its discretion, may also choose
to place any const declarations in read-only storage,
so if you attempt to hack around the const blocks,
you could get undefined behavior. >>
I do not understand if just declaring that a pointer
is to constant data may incur in that problem even
if the pointed data was in fact allocated with malloc.
I would say of course not, but I am not sure.
E.g. consider this little internal helper of mine
(which implements an interface that is public to
do an internal thing...), where I am casting to
pointer to non-constant data in order to free the
pointed data (i.e. without warning):
```c
static int MyStruct_free_(MyStruct_t const *pT) {
assert(pT);
free((MyStruct_t *)pT);
return 0;
}
```
Assuming, as said, that the data was originally
allocated with malloc, is that code safe or
something can go wrong even in that case?
Suppose you have :
int v = 123; // Non-const object definition
const int * cp = &v; // Const pointer to non-const data
int * p = (int *) cp; // Cast to non-const pointer
*p = 456; // Change the target data
This is allowed, because the original object definition was not a const definition.
However, with this:
int v = 123; // Const object definition
const int * cp = &v; // Const pointer to const data
int * p = (int *) cp; // Cast to non-const pointer
*p = 456; // Undefined behaviour
You can make the pointer to non-const, but trying to change an object that was /defined/ as const is undefined behaviour (even if it was not placed in read-only memory).
When you use dynamic memory, however, you are not defining an object in the same way. If you write :
const int * cp = malloc(sizeof(int));
you are defining the object "p" as a pointer to type "const int" - but you are not defining a const int. You can cast "cp" to "int *" and use that--
new pointer to change the value.
David Brown <david.brown@hesbynett.no> writes:
Suppose you have :
int v = 123; // Non-const object definition
const int * cp = &v; // Const pointer to non-const data
int * p = (int *) cp; // Cast to non-const pointer
*p = 456; // Change the target data
This is allowed, because the original object definition was not a const
definition.
However, with this:
int v = 123; // Const object definition
const int * cp = &v; // Const pointer to const data
int * p = (int *) cp; // Cast to non-const pointer
*p = 456; // Undefined behaviour
I think missed out the crucial "const" on the first line of the second example! It's always the way.
You can make the pointer to non-const, but trying to change an object that >> was /defined/ as const is undefined behaviour (even if it was not placed in >> read-only memory).
When you use dynamic memory, however, you are not defining an object in the >> same way. If you write :
const int * cp = malloc(sizeof(int));
I prefer
const int *cp = malloc(sizeof *cp);
you are defining the object "p" as a pointer to type "const int" - but you >> are not defining a const int. You can cast "cp" to "int *" and use that
new pointer to change the value.
On 2025-01-07, Julio Di Egidio <julio@diegidio.name> wrote:<snipped>
In particular, I am using C90, and compiling with
`gcc ... -ansi -pedantic -Wall -Wextra` (as I have
the requirement to ideally support any device).
To the question, I was reading this, but I am not
sure what the quoted passage means:
Matt Stancliff, "So You Think You Can Const?",
<https://matt.sh/sytycc>
<< Your compiler, at its discretion, may also choose
to place any const declarations in read-only storage,
so if you attempt to hack around the const blocks,
you could get undefined behavior. >>
An object defined with a type that is const-qualified
could be put into write-protected storage.
Objects coming from malloc are not defined by a declaration.
ISO C defines the term /effective type/ (// indicates italics)
for the purposes of stating some rules regarding expressions accessing objects. "The /effective type/ of an object that is not a byte array,
for an access to its stored value, is the declared type of the object"
says the N3301 draft of C23 in section 6.5.1 Expressions/General.
A footnote to this sentence clarifies that "allocated objects have no declared type", almost certainly meaning dynamically allocated by
the malloc family.
How you got the "const MyStruct_t *" pointer is that you first
treated the object as "MyStruct_t *", and filled in its members.
Then you cast the pointer to "const MyStruct_t *".
irrespective of how the pointer we are assigning it is declared:
On 07/01/2025 20:32, Julio Di Egidio wrote:<snipped>
In particular, I am using C90, and compiling with
`gcc ... -ansi -pedantic -Wall -Wextra` (as I have
the requirement to ideally support any device).
People who say they want their code to run on anything are invariably
wildly exaggerating.
People who say they want to write strictly
standards-conforming code, especially C90, so that it will run
everywhere, misunderstand the relationship between the C standards and real-world tools.
I would say that the most portable language standard to use would be a subset of C99. Avoid complex numbers, VLAs, and wide/multibyte
characters, and it will be compilable on all but the most obscure compilers. The use of <stdint.h> types make it far easier to write
clear portable code while keeping good efficiency, and many C99 features
let you write clearer, safer, and more efficient code. C90 was probably
a good choice for highly portable code 15-20 years ago, but not now.
(Your use of "malloc" eliminates far more potential devices for the code than choosing C99 ever could.)
When you have a function with a parameter of type "const T * p", this
tells people reading it that the function will only read data via "p",
On 07/01/2025 23:11, Kaz Kylheku wrote:
On 2025-01-07, Julio Di Egidio <julio@diegidio.name> wrote:<snipped>
In particular, I am using C90, and compiling withAn object defined with a type that is const-qualified
`gcc ... -ansi -pedantic -Wall -Wextra` (as I have
the requirement to ideally support any device).
To the question, I was reading this, but I am not
sure what the quoted passage means:
Matt Stancliff, "So You Think You Can Const?",
<https://matt.sh/sytycc>
<< Your compiler, at its discretion, may also choose
to place any const declarations in read-only storage,
so if you attempt to hack around the const blocks,
you could get undefined behavior. >>
could be put into write-protected storage.
What do you/we mean by "object" in this context? (Sorry, I do have forgotten, the glossary to begin with.)
Overall, I am surmising this and only this might go write-protected:
MyStruct_t const T = {...};
While this one allocates a "byte-array", i.e. irrespective of how the
pointer we are assigning it is declared:
MyStruct_t const *pT = malloc(...);
Is my understanding (to that point) correct?
Technically you get an object with no effective type. David's reply
included some references to find out more about the effective type of an object, but it is safe to say that these only come into play if you are messing about with the way you access the allocated storage (for example accessing it as a MyStruct but then later as a floating point object).
Julio Di Egidio <julio@diegidio.name> writes:<snipped>
On 07/01/2025 23:11, Kaz Kylheku wrote:
On 2025-01-07, Julio Di Egidio <julio@diegidio.name> wrote:
To the question, I was reading this, but I am not
sure what the quoted passage means:
Matt Stancliff, "So You Think You Can Const?",
<https://matt.sh/sytycc>
<< Your compiler, at its discretion, may also choose
to place any const declarations in read-only storage,
so if you attempt to hack around the const blocks,
you could get undefined behavior. >>
An object defined with a type that is const-qualified
could be put into write-protected storage.
What do you/we mean by "object" in this context? (Sorry, I do have
forgotten, the glossary to begin with.)
An object (in C) is a contiguous region of storage, the contents of
which can represent values.
Overall, I am surmising this and only this might go write-protected:
MyStruct_t const T = {...};
Yes, though you should extend your concern beyond what might be write-protected. Modifying an object whose type is const qualified is undefined, even if the object is in writable storage.
While this one allocates a "byte-array", i.e. irrespective of how the
pointer we are assigning it is declared:
MyStruct_t const *pT = malloc(...);
Is my understanding (to that point) correct?
Technically you get an object with no effective type.
More relevant to a discussion of const is to ask what you plan to do
with pT since you can't (without a cast) assign any useful value to the allocated object.
On 08/01/2025 09:46, David Brown wrote:
On 07/01/2025 20:32, Julio Di Egidio wrote:<snipped>
In particular, I am using C90, and compiling with
`gcc ... -ansi -pedantic -Wall -Wextra` (as I have
the requirement to ideally support any device).
People who say they want their code to run on anything are invariably
wildly exaggerating.
:) I do have embedded, and FPGAs, and even transpiling to e.g. Wasm,
etc. in mind, my overall idea for now simply being: as long as the
device comes with a C compiler that is not too broken. (I am also
planning to distribute source files only: it also makes my life and
coding so much easier, at the cost of not being able to
"micro-optimize": where I am rather hoping that optimization can still
come down the line if needed as an added pre or post processing step.)
So, you might very well be right that "C90" isn't the best possible
choice not even for my requirement, anyway I am at a pre-alpha stage, I
am sure I will be tightening it up.
People who say they want to write strictly standards-conforming code,
especially C90, so that it will run everywhere, misunderstand the
relationship between the C standards and real-world tools.
So, now that I have qualified it with "any device coming with a C
compiler (that is not too broken)", would you think coding it in "ANSI
C" makes some sense?
I would say that the most portable language standard to use would be a
subset of C99. Avoid complex numbers, VLAs, and wide/multibyte
characters, and it will be compilable on all but the most obscure
compilers. The use of <stdint.h> types make it far easier to write
clear portable code while keeping good efficiency, and many C99
features let you write clearer, safer, and more efficient code. C90
was probably a good choice for highly portable code 15-20 years ago,
but not now. (Your use of "malloc" eliminates far more potential
devices for the code than choosing C99 ever could.)
Assuming I don't in fact care if and how well a compiler does its job
(in fact my policy for now is: as long as it compiles with GCC with
those flags), what is wrong with "malloc"?
When you have a function with a parameter of type "const T * p", this
tells people reading it that the function will only read data via "p",
Never mind, it's a private (static) method, so I am not "lying" to
anybody: rather const and cast and almost everything in C is altogether something else...
On 08/01/2025 16:16, Ben Bacarisse wrote:<snip>
More relevant to a discussion of const is to ask what you plan to do
with pT since you can't (without a cast) assign any useful value to the
allocated object.
Say my program unit implements AVL trees, with (conceptually speaking) constructors/destructors, navigation and retrieval, and of course manipulation (inserting, deleting, etc.).
My idea (but I would think this is pretty "canonical" and, if it isn't,
I am missing the mark) is: my public functions take/give "sealed"
instances (with const members to const data), as the user is not
supposed to directly manipulate/edit the data, OTOH of course my implementation is all about in-place editing...
On 07/01/2025 20:32, Julio Di Egidio wrote:
Hi everybody,
I am back to programming in C after many years:
indeed I have forgotten so many things, including
how much I love this language. :)
In particular, I am using C90, and compiling with
`gcc ... -ansi -pedantic -Wall -Wextra` (as I have
the requirement to ideally support any device).
What devices do not have at least C99 compilers available - and yet /do/ have standard C90 compilers available? What sort of code are you
writing that should ideally run on an AVR Tiny with 2K of flash and 64
bytes of ram, a DSP with 24-bit chars, and a TOP100 supercomputer? Have
you thought about this in more detail?
On 08/01/2025 15:42, Julio Di Egidio wrote:<snip>
On 08/01/2025 09:46, David Brown wrote:
On 07/01/2025 20:32, Julio Di Egidio wrote:<snipped>
In particular, I am using C90, and compiling with
`gcc ... -ansi -pedantic -Wall -Wextra` (as I have
the requirement to ideally support any device).
People who say they want their code to run on anything are invariably
wildly exaggerating.
:) I do have embedded, and FPGAs, and even transpiling to e.g. Wasm,
etc. in mind, my overall idea for now simply being: as long as the
device comes with a C compiler that is not too broken.
Do you have experience with embedded programming (if so, what kind of devices)?
So, now that I have qualified it with "any device coming with a C
compiler (that is not too broken)", would you think coding it in "ANSI
C" makes some sense?
No.
For people using 8051, COP8, 68HC05, PIC16 or other long outdated brain- dead microcontrollers, you don't get standard C support at all. You
program these in a device-specific variant of C full of extensions and
extra restrictions - and the support is as close to the subset of C99
that I described as it is to standard C90.
To the question, I was reading this, but I am not
sure what the quoted passage means:
Matt Stancliff, "So You Think You Can Const?",
<https://matt.sh/sytycc>
<< Your compiler, at its discretion, may also choose
to place any const declarations in read-only storage,
so if you attempt to hack around the const blocks,
you could get undefined behavior. >>
I do not understand if just declaring that a pointer
is to constant data may incur in that problem
E.g. consider this little internal helper of mine
(which implements an interface that is public to
do an internal thing...), where I am casting to
pointer to non-constant data in order to free the
pointed data (i.e. without warning):
```c
static int MyStruct_free_(MyStruct_t const *pT) {
assert(pT);
free((MyStruct_t *)pT);
return 0;
}
```
Assuming, as said, that the data was originally
allocated with malloc, is that code safe or
something can go wrong even in that case?
On 08/01/2025 17:18, David Brown wrote:
On 08/01/2025 15:42, Julio Di Egidio wrote:<snip>
On 08/01/2025 09:46, David Brown wrote:
On 07/01/2025 20:32, Julio Di Egidio wrote:<snipped>
In particular, I am using C90, and compiling with
`gcc ... -ansi -pedantic -Wall -Wextra` (as I have
the requirement to ideally support any device).
People who say they want their code to run on anything are
invariably wildly exaggerating.
:) I do have embedded, and FPGAs, and even transpiling to e.g. Wasm,
etc. in mind, my overall idea for now simply being: as long as the
device comes with a C compiler that is not too broken.
Do you have experience with embedded programming (if so, what kind of
devices)?
TL;DR nearly zero. Siemens PLCs for small industrial automation, my own experimenting with Intel FPGAs (mainly for coprocessors), plus coding against device drivers for systems integration.
So, now that I have qualified it with "any device coming with a C
compiler (that is not too broken)", would you think coding it in
"ANSI C" makes some sense?
No.
Cool. :) Please give me few hours, maybe less: I will be reading your reply with great interest.
On 08/01/2025 09:46, David Brown wrote:...
People who say they want to write strictly
standards-conforming code, especially C90, so that it will run
everywhere, misunderstand the relationship between the C standards and
real-world tools.
So, now that I have qualified it with "any device coming with a C
compiler (that is not too broken)", would you think coding it in "ANSI
C" makes some sense?
On 08/01/2025 16:16, Ben Bacarisse wrote:...
Julio Di Egidio <julio@diegidio.name> writes:
On 07/01/2025 23:11, Kaz Kylheku wrote:
An object defined with a type that is const-qualified
could be put into write-protected storage.
What do you/we mean by "object" in this context? (Sorry, I do have
forgotten, the glossary to begin with.)
An object (in C) is a contiguous region of storage, the contents of
which can represent values.
Is that regardless of the stack/heap distinction, or is an "object"
about heap-allocated/dynamic memory only? -- Anyway, I should in fact re-acquaint myself with the language reference instead of asking this question.)
Objects with automatic storage duration are never defined, so they
cannot be defined to be 'const'.
Hi everybody,
I am back to programming in C after many years:
indeed I have forgotten so many things, including
how much I love this language. :)
In particular, I am using C90, and compiling with
`gcc ... -ansi -pedantic -Wall -Wextra` (as I have
the requirement to ideally support any device).
C89 and C90 are better for 8-bit systems then C99 and newer. Not
that you can't do 8-bit on C99 but it's just not designed as well
for it since C99 assumes you've moved on to at least 16-bit.
int v = 123; // Non-const object definition
const int * cp = &v; // Const pointer to non-const data
int * p = (int *) cp; // Cast to non-const pointer
*p = 456; // Change the target data
Say my program unit implements AVL trees, with (conceptually
speaking) constructors/destructors, navigation and retrieval, and
of course manipulation (inserting, deleting, etc.).
My idea (but I would think this is pretty "canonical" and, if it
isn't, I am missing the mark) is: my public functions take/give
"sealed" instances (with const members to const data), as the user
is not supposed to directly manipulate/edit the data, OTOH of
course my implementation is all about in-place editing...
On 1/8/25 11:05, Julio Di Egidio wrote:
I'm not sufficiently
familiar with multi-threaded programming to comment on how objects with >thread storage duration may be implemented.
On 01/07/25 11:32 AM, Julio Di Egidio wrote:
Assuming, as said, that the data was originally
allocated with malloc, is [calling free on a pointer
to const something] safe or something can go wrong
even in that case?
It is perfectly safe. One can even argue that standard declaration if
free` as `void free(void *)` is defective. It should have been `void free(const void *)` from the very beginning.
Julio Di Egidio <julio@diegidio.name> writes:
On 07/01/2025 23:11, Kaz Kylheku wrote:
On 2025-01-07, Julio Di Egidio <julio@diegidio.name> wrote:
<snipped>
In particular, I am using C90, and compiling with
`gcc ... -ansi -pedantic -Wall -Wextra` (as I have
the requirement to ideally support any device).
To the question, I was reading this, but I am not
sure what the quoted passage means:
Matt Stancliff, "So You Think You Can Const?",
<https://matt.sh/sytycc>
<< Your compiler, at its discretion, may also choose
to place any const declarations in read-only storage,
so if you attempt to hack around the const blocks,
you could get undefined behavior. >>
An object defined with a type that is const-qualified
could be put into write-protected storage.
What do you/we mean by "object" in this context? (Sorry, I do have
forgotten, the glossary to begin with.)
An object (in C) is a contiguous region of storage, the contents of
which can represent values.
Overall, I am surmising this and only this might go write-protected:
MyStruct_t const T = {...};
Yes, though you should extend your concern beyond what might be write-protected. Modifying an object whose type is const qualified
is undefined, even if the object is in writable storage. A compiler
may assume that such an object has not changed because in a program
that has undefined behaviour, all bets are off. [...]
On 01/07/25 11:32 AM, Julio Di Egidio wrote:
To the question, I was reading this, but I am not
sure what the quoted passage means:
Matt Stancliff, "So You Think You Can Const?",
<https://matt.sh/sytycc>
<< Your compiler, at its discretion, may also choose
to place any const declarations in read-only storage,
so if you attempt to hack around the const blocks,
you could get undefined behavior. >>
Strictly speaking, the passage is misleading. It dues not matter
whether the compiler decided to place const data into read-only
storage. If you "hack around" data constness (i.e. if you attempt to
modify const data), you _always_ get undefined behavior, regardless of
where the data is actually stored.
The author of the article likely thought of "undefined behavior" as
"the program crashes" or "something goes terribly wrong". In fact
undefined behavior is simply behavior that is not defined; the C
standard says nothing about what happens.
And if the manifestation of that undefined behavior is that the
code quietly does what you thought it would do, it could mean that
you have a latent bug that's difficult to track down, and that will
come back and bite you later.
On 08/01/2025 16:16, Ben Bacarisse wrote:
Julio Di Egidio <julio@diegidio.name> writes:<snipped>
On 07/01/2025 23:11, Kaz Kylheku wrote:
On 2025-01-07, Julio Di Egidio <julio@diegidio.name> wrote:
To the question, I was reading this, but I am not
sure what the quoted passage means:
Matt Stancliff, "So You Think You Can Const?",
<https://matt.sh/sytycc>
<< Your compiler, at its discretion, may also choose
to place any const declarations in read-only storage,
so if you attempt to hack around the const blocks,
you could get undefined behavior. >>
An object defined with a type that is const-qualified
could be put into write-protected storage.
What do you/we mean by "object" in this context? (Sorry, I do have
forgotten, the glossary to begin with.)
An object (in C) is a contiguous region of storage, the contents of
which can represent values.
Is that regardless of the stack/heap distinction, or is an "object"
about heap-allocated/dynamic memory only? -- Anyway, I should in fact re-acquaint myself with the language reference instead of asking this question.)
Overall, I am surmising this and only this might go write-protected:
MyStruct_t const T = {...};
Yes, though you should extend your concern beyond what might be
write-protected. Modifying an object whose type is const qualified is
undefined, even if the object is in writable storage.
Yes, I am being a bit quick, but I definitely agree with that and indeed
the priority of "defined behaviour" as a concern.
While this one allocates a "byte-array", i.e. irrespective of how the
pointer we are assigning it is declared:
MyStruct_t const *pT = malloc(...);
Is my understanding (to that point) correct?
Technically you get an object with no effective type.
OK.
More relevant to a discussion of const is to ask what you plan to do
with pT since you can't (without a cast) assign any useful value to the
allocated object.
Say my program unit implements AVL trees, with (conceptually speaking) constructors/destructors, navigation and retrieval, and of course manipulation (inserting, deleting, etc.).
My idea (but I would think this is pretty "canonical" and, if it isn't,
I am missing the mark) is: my public functions take/give "sealed"
instances (with const members to const data), as the user is not
supposed to directly manipulate/edit the data, OTOH of course my implementation is all about in-place editing...
On 08/01/2025 17:05, Julio Di Egidio wrote:
On 08/01/2025 16:16, Ben Bacarisse wrote:<snip>
More relevant to a discussion of const is to ask what you plan to do
with pT since you can't (without a cast) assign any useful value to the
allocated object.
Say my program unit implements AVL trees, with (conceptually speaking)
constructors/destructors, navigation and retrieval, and of course
manipulation (inserting, deleting, etc.).
My idea (but I would think this is pretty "canonical" and, if it
isn't, I am missing the mark) is: my public functions take/give
"sealed" instances (with const members to const data), as the user is
not supposed to directly manipulate/edit the data, OTOH of course my
implementation is all about in-place editing...
P.S. To be clear, as I am still being a bit quick: I do not also mean "public destructors" should take a const pointer in input, i.e. apply as appropriate...
And here is what my construction/destruction code is looking like at the moment, which should also make clear what I meant by "a private method implementing a public interface" and why:
```c
static AvlTree_t const *AvlTree_node(
void const *pk, AvlTree_t const *pL, AvlTree_t const *pR
) {
AvlTree_t *pT;
pT = malloc(sizeof(AvlTree_t));
if (!pT) {
return NULL;
}
pT->pk = pk;
pT->pL = pL;
pT->pR = pR;
return pT;
}
static int AvlTree_free_(AvlTree_t const *pT) {
assert(pT);
free((AvlTree_t *)pT);
return 0;
}
AvlTree_t const *AvlTree_create(void const *pk) {
return AvlTree_node(pk, NULL, NULL);
}
void AvlTree_destroy(AvlTree_t *pT) {
AvlTree_visitPost(AvlTree_free_, pT);
}
```
-Julio
Ben Bacarisse <ben@bsb.me.uk> writes:
... Modifying an object whose type is const qualified
is undefined, even if the object is in writable storage. A compiler
may assume that such an object has not changed because in a program
that has undefined behaviour, all bets are off. [...]
We need to be careful about what is being asserted here. There
are cases where a compiler may not assume that a const object
has not changed, despite the rule that assigning to a const
object is undefined behavior:
#include <stdio.h>
typedef union { const int foo; int bas; } Foobas;
int
main(){
Foobas fb = { 0 };
printf( " fb.foo is %d\n", fb.foo );
fb.bas = 7;
printf( " fb.foo is %d\n", fb.foo );
return 0;
}
The object fb.foo is indeed a const object, but an access of
fb.foo must not assume that it retains its original value after
the assignment to fb.bas.
On 08/01/2025 16:16, Ben Bacarisse wrote:
Julio Di Egidio <julio@diegidio.name> writes:<snipped>
On 07/01/2025 23:11, Kaz Kylheku wrote:
On 2025-01-07, Julio Di Egidio <julio@diegidio.name> wrote:
An object (in C) is a contiguous region of storage, the contents ofTo the question, I was reading this, but I am not
sure what the quoted passage means:
Matt Stancliff, "So You Think You Can Const?",
<https://matt.sh/sytycc>
<< Your compiler, at its discretion, may also choose
to place any const declarations in read-only storage,
so if you attempt to hack around the const blocks,
you could get undefined behavior. >>
An object defined with a type that is const-qualified
could be put into write-protected storage.
What do you/we mean by "object" in this context? (Sorry, I do have
forgotten, the glossary to begin with.)
which can represent values.
Is that regardless of the stack/heap distinction, or is an "object" about heap-allocated/dynamic memory only?
-- Anyway, I should in fact
re-acquaint myself with the language reference instead of asking this question.)
Overall, I am surmising this and only this might go write-protected:Yes, though you should extend your concern beyond what might be
MyStruct_t const T = {...};
write-protected. Modifying an object whose type is const qualified is
undefined, even if the object is in writable storage.
Yes, I am being a bit quick, but I definitely agree with that and indeed
the priority of "defined behaviour" as a concern.
While this one allocates a "byte-array", i.e. irrespective of how theTechnically you get an object with no effective type.
pointer we are assigning it is declared:
MyStruct_t const *pT = malloc(...);
Is my understanding (to that point) correct?
OK.
More relevant to a discussion of const is to ask what you plan to do
with pT since you can't (without a cast) assign any useful value to the
allocated object.
Say my program unit implements AVL trees, with (conceptually speaking) constructors/destructors, navigation and retrieval, and of course manipulation (inserting, deleting, etc.).
My idea (but I would think this is pretty "canonical" and, if it isn't, I
am missing the mark) is: my public functions take/give "sealed" instances (with const members to const data), as the user is not supposed to directly manipulate/edit the data, OTOH of course my implementation is all about in-place editing...
static AvlTree_t const *AvlTree_node(
void const *pk, AvlTree_t const *pL, AvlTree_t const *pR
) {
AvlTree_t *pT;
pT = malloc(sizeof(AvlTree_t));
if (!pT) {
return NULL;
}
pT->pk = pk;
pT->pL = pL;
pT->pR = pR;
return pT;
}
Julio Di Egidio <julio@diegidio.name> writes:
static AvlTree_t const *AvlTree_node(
void const *pk, AvlTree_t const *pL, AvlTree_t const *pR
) {
AvlTree_t *pT;
pT = malloc(sizeof(AvlTree_t));
if (!pT) {
return NULL;
}
pT->pk = pk;
pT->pL = pL;
pT->pR = pR;
return pT;
}
Just on a side issue, I prefer to make tests like this positive so I'd
write:
static AvlTree_t const *AvlTree_node(
void const *pk, AvlTree_t const *pL, AvlTree_t const *pR
) {
AvlTree_t *pT = malloc(*pT);
if (pT) {
pT->pk = pk;
pT->pL = pL;
pT->pR = pR;
}
return pT;
}
I'm not going to "make a case" for this (though I will if you want!) --
I just think it helps to see lots of different styles.
Julio Di Egidio <julio@diegidio.name> writes:
static AvlTree_t const *AvlTree_node(
void const *pk, AvlTree_t const *pL, AvlTree_t const *pR
) {
AvlTree_t *pT;
pT = malloc(sizeof(AvlTree_t));
if (!pT) {
return NULL;
}
pT->pk = pk;
pT->pL = pL;
pT->pR = pR;
return pT;
}
Just on a side issue, I prefer to make tests like this positive so I'd
write:
static AvlTree_t const *AvlTree_node(
void const *pk, AvlTree_t const *pL, AvlTree_t const *pR
) {
AvlTree_t *pT = malloc(*pT);
if (pT) {
pT->pk = pk;
pT->pL = pL;
pT->pR = pR;
}
return pT;
}
I'm not going to "make a case" for this (though I will if you want!) --
I just think it helps to see lots of different styles.
Julio Di Egidio <julio@diegidio.name> writes:<snip>
My idea (but I would think this is pretty "canonical" and, if it isn't, I
am missing the mark) is: my public functions take/give "sealed" instances
(with const members to const data), as the user is not supposed to directly >> manipulate/edit the data, OTOH of course my implementation is all about
in-place editing...
See Tim's reply -- the best way to implement "sealed" instances is to
use an opaque type where the "user code" simply can't see anything but a pointer to an otherwise unknown struct.
Julio Di Egidio <julio@diegidio.name> writes:
[...]
Say my program unit implements AVL trees, with (conceptually
speaking) constructors/destructors, navigation and retrieval, and
of course manipulation (inserting, deleting, etc.).
My idea (but I would think this is pretty "canonical" and, if it
isn't, I am missing the mark) is: my public functions take/give
"sealed" instances (with const members to const data), as the user
is not supposed to directly manipulate/edit the data, OTOH of
course my implementation is all about in-place editing...
A better choice is to put the AVL code in a separate .c file,
and give out only opaque types to clients. For example (disclaimer:
not compiled):
// in "avl.h"
typedef struct avl_node_s *AVLTree;
// note that the struct contents are not defined in the .h file
... declare interfaces that accept and return AVLTree values ...
// in "avl.c"
#include "avl.h"
struct avl_node_s {
// whatever members are needed
};
... implementation of public interfaces and any supporting
... functions needed
I might mention that some people don't like declaring a type name
that includes the pointerness ('*') as part of the type. I think
doing that is okay (and in fact more than just okay; better) in the
specific case where the type name is being offered as an opaque
type.
Of course you could also make the opaque type be a pointer to a
'const' struct type, if you wanted to, but the extra "protection" of const-ness doesn't add much, and might actually cost more than it
buys you because of the additional casting that would be needed.
On 08/01/2025 15:42, Julio Di Egidio wrote:<snip>
So you can be confident that almost anyone
using your software in embedded systems will be using a 32-bit core -
most likely an ARM Cortex-M, but possibly RISC-V. And they will
probably be using a toolchain that supports at least C17 (some people
are still on older toolchains), whether it is gcc, clang, or commercial.
Certainly solid C99 support is guaranteed. Everything else is niche, and no one will be using your software on niche systems.
On 01/07/25 11:32 AM, Julio Di Egidio wrote:<snip>
E.g. consider this little internal helper of mine
(which implements an interface that is public to
do an internal thing...), where I am casting to
pointer to non-constant data in order to free the
pointed data (i.e. without warning):
```c
static int MyStruct_free_(MyStruct_t const *pT) {
assert(pT);
free((MyStruct_t *)pT);
return 0;
}
```
Assuming, as said, that the data was originally
allocated with malloc, is that code safe or
something can go wrong even in that case?
It is perfectly safe. One can even argue that standard declaration if
`free` as `void free(void *)` is defective. It should have been `void free(const void *)` from the very beginning.
On 08/01/2025 17:18, David Brown wrote:
On 08/01/2025 15:42, Julio Di Egidio wrote:<snip>
So you can be confident that almost anyone using your software in
embedded systems will be using a 32-bit core - most likely an ARM
Cortex-M, but possibly RISC-V. And they will probably be using a
toolchain that supports at least C17 (some people are still on older
toolchains), whether it is gcc, clang, or commercial. Certainly
solid C99 support is guaranteed. Everything else is niche, and no one
will be using your software on niche systems.
Even my fridge should be able to run it... I am writing a Prolog
compiler, but more generally I'd be mostly writing algorithms-data structures things.
That said, one thing nobody has been explaining is why C99 is superior
to C89/C90, except for some coding conveniences as far as I have read online: and I must say here that I do prefer the good old ways,
including style-wise, in most cases...
But, more concretely, what I do not understand of your reply is, OK the plethora of architectures and compilers and languages, but I cannot even begin to cope with that, can I, and why should I when I can e.g. just
have a "config" header (up to even a pre-preprocessor or whatever pre or post-transformations are needed) where I re-define "malloc" or even
"int" as I like for a specific target?
The underlying idea being I won't care at all, I just pick a reasonable
and reasonably standard variant of the C language as base, and I just distribute source code, the user must compile it.
On 2025-01-09, Ben Bacarisse <ben@bsb.me.uk> wrote:
Julio Di Egidio <julio@diegidio.name> writes:
static AvlTree_t const *AvlTree_node(
void const *pk, AvlTree_t const *pL, AvlTree_t const *pR
) {
AvlTree_t *pT;
pT = malloc(sizeof(AvlTree_t));
if (!pT) {
return NULL;
}
pT->pk = pk;
pT->pL = pL;
pT->pR = pR;
return pT;
}
Just on a side issue, I prefer to make tests like this positive so I'd
write:
static AvlTree_t const *AvlTree_node(
void const *pk, AvlTree_t const *pL, AvlTree_t const *pR
) {
AvlTree_t *pT = malloc(*pT);
if (pT) {
pT->pk = pk;
pT->pL = pL;
pT->pR = pR;
}
return pT;
}
More generally:
foo_handle *foo = foo_create();
bar_handle *bar = foo ? bar_create(foo) : 0; // doesn't like null
xyzzy_handle *xyz = xyzzy_create(42, bar, arg);
container *con = malloc(sizeof *con);
if (foo && bar && xyz && con) {
// happy case: we have all three resources
con->foo = foo;
con->bar = bar;
con->xyz = xyz;
return con;
}
xyzzy_destroy(xyz);
xyzzy_destroy(bar);
if (foo)
xyzzy_destroy(foo); // stupidly doesn't like null
return 0;
I'm not going to "make a case" for this (though I will if you want!) --
I just think it helps to see lots of different styles.
I might just have made the case. When more resources need to be
acquired that might fail, it consolidates the happy case under one conjunctive test, and consolidates the cleanup in the unhappy case. Effectively it's almost if we have only two cases.
A minor disadvantage is that in the unhappy flow, we may allocate
resources past the point where it is obvious they are not going to be
needed: if foo_create() failed, we are pointlessly calling
xyzzy_create() and malloc for the container. It's possible that these succeed, and we are just going to turn around and free them.
It's a form of consolidated error checking, like when we make
several system calls and check them for errors as a batch;
e.g. call fprintf several times and check for disk full (etc)
just once.
On 08/01/2025 17:48, Andrey Tarasevich wrote:[...]
It is perfectly safe. One can even argue that standard declaration
if `free` as `void free(void *)` is defective. It should have been
`void free(const void *)` from the very beginning.
I do not understand that: `free` is changing the pointed data, so how
can `const void *` even be "correct"?
Julio Di Egidio <julio@diegidio.name> writes:
On 08/01/2025 17:48, Andrey Tarasevich wrote:[...]
It is perfectly safe. One can even argue that standard declaration
if `free` as `void free(void *)` is defective. It should have been
`void free(const void *)` from the very beginning.
I do not understand that: `free` is changing the pointed data, so how
can `const void *` even be "correct"?
No, `free` doesn't (necessarily) change the pointed-to data.
Any attempt to access the allocated data after free() has undefined
behavior,
so it might be modified, but all free() needs to do is
make it available for further allocation. It might do so without
touching the data itself.
On 1/8/25 11:18 AM, David Brown wrote:
For people using 8051, COP8, 68HC05, PIC16 or other long outdated
brain- dead microcontrollers, you don't get standard C support at all.
You program these in a device-specific variant of C full of extensions
and extra restrictions - and the support is as close to the subset of
C99 that I described as it is to standard C90.
Just a point of reference, there are still several "brain-dead" systems
in modern use today that aren't old, some being invested as late as
2019.
That being said, your comment isn't completely accurate in that,
there are some modern uses of things like the 6502 that can use standards-based C. In fact, you can use ANSI C89 and C90 with the 6502.
I've done this for several modern pace makers as well as a smart
prosthetic. So your statement is correct in 90% of cases but not all cases.
(Also most car manufacturer's use the 6502 and other variants for their digital input analog gauges and warning light controls on their
dashboards.)
C89 and C90 are better for 8-bit systems then C99 and newer. Not that
you can't do 8-bit on C99 but it's just not designed as well for it
since C99 assumes you've moved on to at least 16-bit.
But this is all based on the OP's specific use case for their
application. I just wanted to chime in since I do primarily work on
modern embedded systems that don't use "modern" microcontrollers and
CPU's since they are still used in a wide range of modern devices that people don't even realize.
On 08/01/2025 17:18, David Brown wrote:
On 08/01/2025 15:42, Julio Di Egidio wrote:<snip>
So you can be confident that almost anyone using your software in
embedded systems will be using a 32-bit core - most likely an ARM
Cortex-M, but possibly RISC-V. And they will probably be using a
toolchain that supports at least C17 (some people are still on older
toolchains), whether it is gcc, clang, or commercial. Certainly
solid C99 support is guaranteed. Everything else is niche, and no one
will be using your software on niche systems.
Even my fridge should be able to run it... I am writing a Prolog
compiler, but more generally I'd be mostly writing algorithms-data structures things.
That said, one thing nobody has been explaining is why C99 is superior
to C89/C90, except for some coding conveniences as far as I have read online: and I must say here that I do prefer the good old ways,
including style-wise, in most cases...
It is perfectly safe. One can even argue that standard declaration if
`free` as `void free(void *)` is defective. It should have been `void free(const void *)` from the very beginning.
On 09/01/2025 09:07, Julio Di Egidio wrote:
On 08/01/2025 17:18, David Brown wrote:
On 08/01/2025 15:42, Julio Di Egidio wrote:<snip>
So you can be confident that almost anyone using your software in
embedded systems will be using a 32-bit core - most likely an ARM
Cortex-M, but possibly RISC-V. And they will probably be using a
toolchain that supports at least C17 (some people are still on older
toolchains), whether it is gcc, clang, or commercial. Certainly
solid C99 support is guaranteed. Everything else is niche, and no
one will be using your software on niche systems.
Even my fridge should be able to run it... I am writing a Prolog
compiler, but more generally I'd be mostly writing algorithms-data
structures things.
That said, one thing nobody has been explaining is why C99 is superior
to C89/C90, except for some coding conveniences as far as I have read
online: and I must say here that I do prefer the good old ways,
including style-wise, in most cases...
The C99 features that I consider to make code easier to write, clearer, safer, more portable, more efficient, and generally better are:
compound literals
designated initialisers
mixing declaration and code
variadic macros
Julio Di Egidio <julio@diegidio.name> writes:
On 08/01/2025 17:48, Andrey Tarasevich wrote:[...]
It is perfectly safe. One can even argue that standard declaration
if `free` as `void free(void *)` is defective. It should have been
`void free(const void *)` from the very beginning.
I do not understand that: `free` is changing the pointed data, so how
can `const void *` even be "correct"?
No, `free` doesn't (necessarily) change the pointed-to data.
Any attempt to access the allocated data after free() has undefined
behavior, so it might be modified, but all free() needs to do is
make it available for further allocation. It might do so without
touching the data itself.
On 09/01/2025 14:11, David Brown wrote:
On 09/01/2025 09:07, Julio Di Egidio wrote:
On 08/01/2025 17:18, David Brown wrote:
On 08/01/2025 15:42, Julio Di Egidio wrote:<snip>
So you can be confident that almost anyone using your software in
embedded systems will be using a 32-bit core - most likely an ARM
Cortex-M, but possibly RISC-V. And they will probably be using a
toolchain that supports at least C17 (some people are still on older
toolchains), whether it is gcc, clang, or commercial. Certainly
solid C99 support is guaranteed. Everything else is niche, and no
one will be using your software on niche systems.
Even my fridge should be able to run it... I am writing a Prolog
compiler, but more generally I'd be mostly writing algorithms-data
structures things.
That said, one thing nobody has been explaining is why C99 is
superior to C89/C90, except for some coding conveniences as far as I
have read online: and I must say here that I do prefer the good old
ways, including style-wise, in most cases...
The C99 features that I consider to make code easier to write,
clearer, safer, more portable, more efficient, and generally better are:
compound literals
designated initialisers
mixing declaration and code
variadic macros
Funny, I usually find code using such features less clear!
They would also make programs a little less portable, since they now
rely on an implementation that includes support.
On 09/01/2025 05:24, Kaz Kylheku wrote:
On 2025-01-09, Ben Bacarisse <ben@bsb.me.uk> wrote:
Julio Di Egidio <julio@diegidio.name> writes:
static AvlTree_t const *AvlTree_node(
void const *pk, AvlTree_t const *pL, AvlTree_t const *pR
) {
AvlTree_t *pT;
pT = malloc(sizeof(AvlTree_t));
if (!pT) {
return NULL;
}
pT->pk = pk;
pT->pL = pL;
pT->pR = pR;
return pT;
}
Just on a side issue, I prefer to make tests like this positive so I'd
write:
static AvlTree_t const *AvlTree_node(
void const *pk, AvlTree_t const *pL, AvlTree_t const *pR
) {
AvlTree_t *pT = malloc(*pT);
if (pT) {
pT->pk = pk;
pT->pL = pL;
pT->pR = pR;
}
return pT;
}
More generally:
foo_handle *foo = foo_create();
bar_handle *bar = foo ? bar_create(foo) : 0; // doesn't like null
xyzzy_handle *xyz = xyzzy_create(42, bar, arg);
container *con = malloc(sizeof *con);
if (foo && bar && xyz && con) {
// happy case: we have all three resources
con->foo = foo;
con->bar = bar;
con->xyz = xyz;
return con;
}
xyzzy_destroy(xyz);
xyzzy_destroy(bar);
if (foo)
xyzzy_destroy(foo); // stupidly doesn't like null
return 0;
I'm not going to "make a case" for this (though I will if you want!) --
I just think it helps to see lots of different styles.
I might just have made the case. When more resources need to be
acquired that might fail, it consolidates the happy case under one
conjunctive test, and consolidates the cleanup in the unhappy case.
Effectively it's almost if we have only two cases.
A minor disadvantage is that in the unhappy flow, we may allocate
resources past the point where it is obvious they are not going to be
needed: if foo_create() failed, we are pointlessly calling
xyzzy_create() and malloc for the container. It's possible that these
succeed, and we are just going to turn around and free them.
How about taking the idea slightly further and making the later
allocations conditional too?
foo_handle *foo = foo_create();
bar_handle *bar = foo ? bar_create(foo) : 0; // doesn't like null
xyzzy_handle *xyz = bar ? xyzzy_create(42, bar, arg) : 0;
container *con = xyz ? malloc(sizeof *con) : 0;
if (con) {
// happy case: we have all three resources
...
If you are going to use that style (and I not arguing for or against
it), go all in!
It's a form of consolidated error checking, like when we make
several system calls and check them for errors as a batch;
e.g. call fprintf several times and check for disk full (etc)
just once.
On 09/01/2025 02:09, Ben Bacarisse wrote:
Julio Di Egidio <julio@diegidio.name> writes:
static AvlTree_t const *AvlTree_node(Just on a side issue, I prefer to make tests like this positive so I'd
void const *pk, AvlTree_t const *pL, AvlTree_t const *pR
) {
AvlTree_t *pT;
pT = malloc(sizeof(AvlTree_t));
if (!pT) {
return NULL;
}
pT->pk = pk;
pT->pL = pL;
pT->pR = pR;
return pT;
}
write:
static AvlTree_t const *AvlTree_node(
void const *pk, AvlTree_t const *pL, AvlTree_t const *pR
) {
AvlTree_t *pT = malloc(*pT);
if (pT) {
pT->pk = pk;
pT->pL = pL;
pT->pR = pR;
}
return pT;
}
I'm not going to "make a case" for this (though I will if you want!) --
I just think it helps to see lots of different styles.
That is *more* error prone,
all the more so if it's not a 5 liner...--
Julio Di Egidio <julio@diegidio.name> writes:
On 09/01/2025 02:09, Ben Bacarisse wrote:
Julio Di Egidio <julio@diegidio.name> writes:
static AvlTree_t const *AvlTree_node(Just on a side issue, I prefer to make tests like this positive so I'd
void const *pk, AvlTree_t const *pL, AvlTree_t const *pR
) {
AvlTree_t *pT;
pT = malloc(sizeof(AvlTree_t));
if (!pT) {
return NULL;
}
pT->pk = pk;
pT->pL = pL;
pT->pR = pR;
return pT;
}
write:
static AvlTree_t const *AvlTree_node(
void const *pk, AvlTree_t const *pL, AvlTree_t const *pR
) {
AvlTree_t *pT = malloc(*pT);
if (pT) {
pT->pk = pk;
pT->pL = pL;
pT->pR = pR;
}
return pT;
}
I'm not going to "make a case" for this (though I will if you want!) --
I just think it helps to see lots of different styles.
That is *more* error prone,
I would be happy for you to expand on why you say that.
all the more so if it's not a 5 liner...
On 10/01/2025 00:23, Ben Bacarisse wrote:
Julio Di Egidio <julio@diegidio.name> writes:
On 09/01/2025 02:09, Ben Bacarisse wrote:
Julio Di Egidio <julio@diegidio.name> writes:
static AvlTree_t const *AvlTree_node(Just on a side issue, I prefer to make tests like this positive so I'd >>>> write:
void const *pk, AvlTree_t const *pL, AvlTree_t const *pR
) {
AvlTree_t *pT;
pT = malloc(sizeof(AvlTree_t));
if (!pT) {
return NULL;
}
pT->pk = pk;
pT->pL = pL;
pT->pR = pR;
return pT;
}
static AvlTree_t const *AvlTree_node(
void const *pk, AvlTree_t const *pL, AvlTree_t const *pR
) {
AvlTree_t *pT = malloc(*pT);
if (pT) {
pT->pk = pk;
pT->pL = pL;
pT->pR = pR;
}
return pT;
}
I'm not going to "make a case" for this (though I will if you want!) -- >>>> I just think it helps to see lots of different styles.
That is *more* error prone,
I would be happy for you to expand on why you say that.
all the more so if it's not a 5 liner...
There is no such thing as expanding 40 years of professional experience
in software engineering and programming and doing it properly since day
one: just think about that code and what I said for what it's worth, in particular I haven't mentioned 5 liners by chance, things are quite more complicated not in vitro.
And please do not hold a grudge about that: it's not me who was trying
to say how to write code... ;)
HTH,
On 10/01/2025 00:23, Ben Bacarisse wrote:
Julio Di Egidio <julio@diegidio.name> writes:
On 09/01/2025 02:09, Ben Bacarisse wrote:I would be happy for you to expand on why you say that.
Julio Di Egidio <julio@diegidio.name> writes:
static AvlTree_t const *AvlTree_node(Just on a side issue, I prefer to make tests like this positive so I'd >>>> write:
void const *pk, AvlTree_t const *pL, AvlTree_t const *pR
) {
AvlTree_t *pT;
pT = malloc(sizeof(AvlTree_t));
if (!pT) {
return NULL;
}
pT->pk = pk;
pT->pL = pL;
pT->pR = pR;
return pT;
}
static AvlTree_t const *AvlTree_node(
void const *pk, AvlTree_t const *pL, AvlTree_t const *pR
) {
AvlTree_t *pT = malloc(*pT);
if (pT) {
pT->pk = pk;
pT->pL = pL;
pT->pR = pR;
}
return pT;
}
I'm not going to "make a case" for this (though I will if you want!) -- >>>> I just think it helps to see lots of different styles.
That is *more* error prone,
all the more so if it's not a 5 liner...
There is no such thing as expanding 40 years of professional experience in software engineering and programming and doing it properly since day one: just think about that code and what I said for what it's worth, in
particular I haven't mentioned 5 liners by chance, things are quite more complicated not in vitro.
And please do not hold a grudge about that: it's not me who was trying to
say how to write code... ;)
HTH,
On 10/01/2025 00:37, Julio Di Egidio wrote:
On 10/01/2025 00:23, Ben Bacarisse wrote:
Julio Di Egidio <julio@diegidio.name> writes:
On 09/01/2025 02:09, Ben Bacarisse wrote:
Julio Di Egidio <julio@diegidio.name> writes:
static AvlTree_t const *AvlTree_node(
void const *pk, AvlTree_t const *pL, AvlTree_t const *pR
) {
AvlTree_t *pT;
pT = malloc(sizeof(AvlTree_t));
if (!pT) {
return NULL;
}
pk = pk;
pL = pL;
pR = pR;
return pT;
}
Just on a side issue, I prefer to make tests like this positive
so I'd write:
static AvlTree_t const *AvlTree_node(
void const *pk, AvlTree_t const *pL, AvlTree_t const *pR
) {
AvlTree_t *pT = malloc(*pT);
if (pT) {
pk = pk;}
pL = pL;
pR = pR;
return pT;
}
I'm not going to "make a case" for this (though I will if you
want!) -- I just think it helps to see lots of different styles.
That is *more* error prone,
I would be happy for you to expand on why you say that.
all the more so if it's not a 5 liner...
There is no such thing as expanding 40 years of professional
experience in software engineering and programming and doing it
properly since day one: just think about that code and what I said
for what it's worth, in particular I haven't mentioned 5 liners by
chance, things are quite more complicated not in vitro.
And please do not hold a grudge about that: it's not me who was
trying to say how to write code... ;)
BTW, I hadn't mention it, but have you noticed the second one is
misindented? Between me and you, I can tell how long a piece of
code will take to break when in production by just looking at
it... A lot of fun. :)
On 2025-01-09, Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
Julio Di Egidio <julio@diegidio.name> writes:
On 08/01/2025 17:48, Andrey Tarasevich wrote:
[...]
It is perfectly safe. One can even argue that standard declaration
if `free` as `void free(void *)` is defective. It should have been
`void free(const void *)` from the very beginning.
I do not understand that: `free` is changing the pointed data, so
how can `const void *` even be "correct"?
No, `free` doesn't (necessarily) change the pointed-to data.
Any attempt to access the allocated data after free() has undefined
behavior, so it might be modified, but all free() needs to do is
make it available for further allocation. It might do so without
touching the data itself.
It doesn't matter because if free were to change the pointed-to
data, that would be only wrong if the effective type were const.
[...]
Julio Di Egidio <julio@diegidio.name> writes:
On 10/01/2025 00:37, Julio Di Egidio wrote:
On 10/01/2025 00:23, Ben Bacarisse wrote:
Julio Di Egidio <julio@diegidio.name> writes:
On 09/01/2025 02:09, Ben Bacarisse wrote:
Julio Di Egidio <julio@diegidio.name> writes:
static AvlTree_t const *AvlTree_node(
void const *pk, AvlTree_t const *pL, AvlTree_t const *pR
) {
AvlTree_t *pT;
pT = malloc(sizeof(AvlTree_t));
if (!pT) {
return NULL;
}
pT->pk = pk;
pT->pL = pL;
pT->pR = pR;
return pT;
}
Just on a side issue, I prefer to make tests like this positive
so I'd write:
static AvlTree_t const *AvlTree_node(
void const *pk, AvlTree_t const *pL, AvlTree_t const *pR
) {
AvlTree_t *pT = malloc(*pT);
if (pT) {
pT->pk = pk;
pT->pL = pL;
pT->pR = pR;
}
return pT;
}
I'm not going to "make a case" for this (though I will if you
want!) -- I just think it helps to see lots of different styles.
That is *more* error prone,
I would be happy for you to expand on why you say that.
all the more so if it's not a 5 liner...
There is no such thing as expanding 40 years of professional
experience in software engineering and programming and doing it
properly since day one: just think about that code and what I said
for what it's worth, in particular I haven't mentioned 5 liners by
chance, things are quite more complicated not in vitro.
And please do not hold a grudge about that: it's not me who was
trying to say how to write code... ;)
BTW, I hadn't mention it, but have you noticed the second one is
misindented? Between me and you, I can tell how long a piece of
code will take to break when in production by just looking at
it... A lot of fun. :)
The indentation was correct in Ben's original posting.
The misindentation first appeared in your followup to that
posting, where the quoted portion had been changed to remove a
blank line and over-indent the if().
On 10/01/2025 02:43, Tim Rentsch wrote:
Julio Di Egidio <julio@diegidio.name> writes:
On 10/01/2025 00:37, Julio Di Egidio wrote:
On 10/01/2025 00:23, Ben Bacarisse wrote:
Julio Di Egidio <julio@diegidio.name> writes:
On 09/01/2025 02:09, Ben Bacarisse wrote:
Julio Di Egidio <julio@diegidio.name> writes:
static AvlTree_t const *AvlTree_node(
void const *pk, AvlTree_t const *pL, AvlTree_t const *pR
) {
AvlTree_t *pT;
pT = malloc(sizeof(AvlTree_t));
if (!pT) {
return NULL;
}
pT->pk = pk;
pT->pL = pL;
pT->pR = pR;
return pT;
}
Just on a side issue, I prefer to make tests like this positive
so I'd write:
static AvlTree_t const *AvlTree_node(
void const *pk, AvlTree_t const *pL, AvlTree_t const *pR
) {
AvlTree_t *pT = malloc(*pT);
if (pT) {
pT->pk = pk;
pT->pL = pL;
pT->pR = pR;
}
return pT;
}
I'm not going to "make a case" for this (though I will if you
want!) -- I just think it helps to see lots of different styles.
That is *more* error prone,
I would be happy for you to expand on why you say that.
all the more so if it's not a 5 liner...
There is no such thing as expanding 40 years of professional
experience in software engineering and programming and doing it
properly since day one: just think about that code and what I said
for what it's worth, in particular I haven't mentioned 5 liners by
chance, things are quite more complicated not in vitro.
And please do not hold a grudge about that: it's not me who was
trying to say how to write code... ;)
BTW, I hadn't mention it, but have you noticed the second one is
misindented? Between me and you, I can tell how long a piece of
code will take to break when in production by just looking at
it... A lot of fun. :)
The indentation was correct in Ben's original posting.
The misindentation first appeared in your followup to that
posting, where the quoted portion had been changed to remove a
blank line and over-indent the if().
But indeed the point is what happens in the long run: if you look above
mine is still better indented... :) But of course it is not indentation per se the problem: for example check the return value as soon as the function returns a possibly null pointer or an error value is certainly
more widely applicable, and quite less error prone, especially if it's
not a 5 liner... Anyway, I also truly believe there is no point in belabouring the point: it's the overall picture that one must get to see.
On 10/01/2025 03:14, Julio Di Egidio wrote:
On 10/01/2025 02:43, Tim Rentsch wrote:
Julio Di Egidio <julio@diegidio.name> writes:
On 10/01/2025 00:37, Julio Di Egidio wrote:
On 10/01/2025 00:23, Ben Bacarisse wrote:
Julio Di Egidio <julio@diegidio.name> writes:
On 09/01/2025 02:09, Ben Bacarisse wrote:
Julio Di Egidio <julio@diegidio.name> writes:That is *more* error prone,
static AvlTree_t const *AvlTree_node(
void const *pk, AvlTree_t const *pL, AvlTree_t const *pR
) {
AvlTree_t *pT;
pT = malloc(sizeof(AvlTree_t));
if (!pT) {
return NULL;
}
pT->pk = pk;
pT->pL = pL;
pT->pR = pR;
return pT;
}
Just on a side issue, I prefer to make tests like this positive >>>>>>>> so I'd write:
static AvlTree_t const *AvlTree_node(
void const *pk, AvlTree_t const *pL, AvlTree_t const *pR
) {
AvlTree_t *pT = malloc(*pT);
if (pT) {
pT->pk = pk;
pT->pL = pL;
pT->pR = pR;
}
return pT;
}
I'm not going to "make a case" for this (though I will if you
want!) -- I just think it helps to see lots of different styles. >>>>>>>
I would be happy for you to expand on why you say that.
all the more so if it's not a 5 liner...
There is no such thing as expanding 40 years of professional
experience in software engineering and programming and doing it
properly since day one: just think about that code and what I said >>>>> for what it's worth, in particular I haven't mentioned 5 liners by
chance, things are quite more complicated not in vitro.
And please do not hold a grudge about that: it's not me who was
trying to say how to write code... ;)
BTW, I hadn't mention it, but have you noticed the second one is
misindented? Between me and you, I can tell how long a piece of
code will take to break when in production by just looking at
it... A lot of fun. :)
The indentation was correct in Ben's original posting.
The misindentation first appeared in your followup to that
posting, where the quoted portion had been changed to remove a
blank line and over-indent the if().
But indeed the point is what happens in the long run: if you look
above mine is still better indented... :) But of course it is not
indentation per se the problem: for example check the return value as
soon as the function returns a possibly null pointer or an error value
is certainly more widely applicable, and quite less error prone,
especially if it's
I meant: immediately check the return value and bail out if needed. The other approach does not even simplify on the clean-up, by the way...
not a 5 liner... Anyway, I also truly believe there is no point in
belabouring the point: it's the overall picture that one must get to see.
I do not understand that: `free` is changing the pointed data, so how
can `const void *` even be "correct"?
It is common in simple heap implementations for the allocated block to contain data about the block, such as allocation sizes and pointers to
other blocks, in memory just below the address returned by malloc.
free() then uses its parameter to access that data, and may change it.
So "void free(const void *);" would be lying to the user.
Even without that, since you are now giving away the memory for re-use
by other code, it's reasonable to say that "free" might change the data pointed to. (And a security-paranoid "free" might zero out the memory before returning it to the heap for re-use.)
On 01/09/25 12:12 AM, Julio Di Egidio wrote:
I do not understand that: `free` is changing the pointed data, so
how can `const void *` even be "correct"?
`free` is destroying the pointed data.
Every object in C object model has to be created (when its lifetime
begins) and has to be eventually destroyed (when its lifetime
ends). This applies to all objects, including `const` ones
(!). Lifetime of a `const` objects also ends eventually, which means
that `const` object has to be destroyable. No way around it.
Andrey Tarasevich <andreytarasevich@hotmail.com> writes:
On 01/09/25 12:12 AM, Julio Di Egidio wrote:
I do not understand that: `free` is changing the pointed data, so
how can `const void *` even be "correct"?
`free` is destroying the pointed data.
Right. In other words, it causes the pointed-to data to reach the end
of its lifetime. "Changing" the data generally means modifying its
value (that's what "const" forbids).
Given:
int *ptr = malloc(sizeof *ptr);
*ptr = 42;
printf("*ptr = %d\n", *ptr);
free(ptr);
After the call to free(), the int object logically no longer exists.
Also, the value of the pointer object ptr becomes indeterminate.
Attempting to refer to the value of either ptr or *ptr has undefined behavior.
Having said that, it's likely that such an attempt will not be
diagnosed, and that the values of ptr and *ptr will be *appear* to be
the same before and after calling free(). (Though the memory
management system might update *ptr, depending on the
implementation.) But this is outside the scope of what C defines,
and there are no guarantees of *anything*.
Every object in C object model has to be created (when its lifetime
begins) and has to be eventually destroyed (when its lifetime
ends). This applies to all objects, including `const` ones
(!). Lifetime of a `const` objects also ends eventually, which means
that `const` object has to be destroyable. No way around it.
An object with static storage duration (either defined with the
"static" keyword or defined at file scope) has a lifetime that ends
when the program terminates. In a typical implementation, the
destruction of such an object doesn't do anything other than
deallocating its memory.
[...]
On 01/09/25 6:18 AM, David Brown wrote:
It is common in simple heap implementations for the allocated block to
contain data about the block, such as allocation sizes and pointers to
other blocks, in memory just below the address returned by malloc.
free() then uses its parameter to access that data, and may change it.
So "void free(const void *);" would be lying to the user.
No, it wouldn't be. A deallocated data no longer exists from the user's point of view. There's nothing to lie about.
Even without that, since you are now giving away the memory for re-use
by other code, it's reasonable to say that "free" might change the
data pointed to. (And a security-paranoid "free" might zero out the
memory before returning it to the heap for re-use.)
Such internal implementation details are completely irrelevant to the
matter at hand, which is purely conceptual in essence. The rest I've explained above.
On Thu, 09 Jan 2025 23:40:52 -0800...
Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
After the call to free(), the int object logically no longer exists.
Also, the value of the pointer object ptr becomes indeterminate.
Attempting to refer to the value of either ptr or *ptr has undefined
behavior.
I believe that the Standard really says that, but find the part about
value of ptr variable ridiculous. It breaks natural hierarchy by which standard library is somewhat special, but it is not above rules of core language. free() is defined as function rather than macro. By rules of
core language, a function call can not modify the value of local
variable at caller's scope, unless pointers to the variable was passed
to it explicitly.
On 09/01/2025 16:28, bart wrote:
On 09/01/2025 14:11, David Brown wrote:
On 09/01/2025 09:07, Julio Di Egidio wrote:
On 08/01/2025 17:18, David Brown wrote:
On 08/01/2025 15:42, Julio Di Egidio wrote:<snip>
So you can be confident that almost anyone using your software in >>>>> embedded systems will be using a 32-bit core - most likely an ARM
Cortex-M, but possibly RISC-V. And they will probably be using a
toolchain that supports at least C17 (some people are still on
older toolchains), whether it is gcc, clang, or commercial.
Certainly solid C99 support is guaranteed. Everything else is >>>>> niche, and no one will be using your software on niche systems.
Even my fridge should be able to run it... I am writing a Prolog
compiler, but more generally I'd be mostly writing algorithms-data
structures things.
That said, one thing nobody has been explaining is why C99 is
superior to C89/C90, except for some coding conveniences as far as I
have read online: and I must say here that I do prefer the good old
ways, including style-wise, in most cases...
The C99 features that I consider to make code easier to write,
clearer, safer, more portable, more efficient, and generally better are: >>>
compound literals
designated initialisers
mixing declaration and code
variadic macros
Funny, I usually find code using such features less clear!
Opinions on clarity vary - I was giving /my/ opinion.
They would also make programs a little less portable, since they now
rely on an implementation that includes support.
I explained elsewhere how C99 is at least as portable as C90 in real
life - no compiler of relevance can be used with standard C90 and not
with standard C99 (perhaps excluding a few features not on my list -
VLAs, complex numbers and wide character support). And even if you use nothing else from C99, use of <stdint.h> types improves portability significantly.
On Thu, 09 Jan 2025 23:40:52 -0800
Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
Andrey Tarasevich <andreytarasevich@hotmail.com> writes:
On 01/09/25 12:12 AM, Julio Di Egidio wrote:
I do not understand that: `free` is changing the pointed data, so
how can `const void *` even be "correct"?
`free` is destroying the pointed data.
Right. In other words, it causes the pointed-to data to reach the end
of its lifetime. "Changing" the data generally means modifying its
value (that's what "const" forbids).
Given:
int *ptr = malloc(sizeof *ptr);
*ptr = 42;
printf("*ptr = %d\n", *ptr);
free(ptr);
After the call to free(), the int object logically no longer exists.
Also, the value of the pointer object ptr becomes indeterminate.
Attempting to refer to the value of either ptr or *ptr has undefined
behavior.
I believe that the Standard really says that, but find the part about
value of ptr variable ridiculous. It breaks natural hierarchy by which standard library is somewhat special, but it is not above rules of core language. free() is defined as function rather than macro. By rules of
core language, a function call can not modify the value of local
variable at caller's scope, unless pointers to the variable was passed
to it explicitly.
On 01/09/25 12:12 AM, Julio Di Egidio wrote:
I do not understand that: `free` is changing the pointed data, so how
can `const void *` even be "correct"?
`free` is destroying the pointed data.
Every object in C object model has to be created (when its lifetime
begins) and has to be eventually destroyed (when its lifetime ends).
So, destruction is not really a "modifying" operation. Destruction of an
If you want a better signature for "free", then I would suggest "void free(void ** p)" - that (to me) more naturally shows that the function
is freeing the pointer, while also greatly reducing the "use after
free" errors in C code by turning them into "dereferencing a null
pointer" errors which are more easily caught by many OS's.
Michael S <already5chosen@yahoo.com> writes:
On Thu, 09 Jan 2025 23:40:52 -0800
Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
I believe that the Standard really says that, but find the part about
value of ptr variable ridiculous. It breaks natural hierarchy by which
standard library is somewhat special, but it is not above rules of core
language. free() is defined as function rather than macro. By rules of
core language, a function call can not modify the value of local
variable at caller's scope, unless pointers to the variable was passed
to it explicitly.
Right, the call to free() doesn't change the value of ptr.
But that value, which is a valid pointer value before the call, becomes >indeterminate after the call.
You can even do something like this:
int *ptr = malloc(sizeof *ptr); // assume malloc succeeded
int *ptr1 = ptr + 1;
free(ptr - 1);
On 2025-01-10, Andrey Tarasevich <andreytarasevich@hotmail.com> wrote:
On 01/09/25 12:12 AM, Julio Di Egidio wrote:
I do not understand that: `free` is changing the pointed data, so how
can `const void *` even be "correct"?
`free` is destroying the pointed data.
Every object in C object model has to be created (when its lifetime
begins) and has to be eventually destroyed (when its lifetime ends).
That is not so. Literals can be put into a ROM image.
Well, sure, that is created in a factory, and destroyed when recycled.
The point is that the data's lifetime can span over countless
invocations of the program; the program can never observe a time which
is outside of the lifetime of those objects.
So, destruction is not really a "modifying" operation. Destruction of an
Destruction by malloc is modifying in any system that recycles the
memory for another allocation.
Keith Thompson <Keith.S.Thompson+u@gmail.com> writes:[...]
Michael S <already5chosen@yahoo.com> writes:
You can even do something like this:
int *ptr = malloc(sizeof *ptr); // assume malloc succeeded
int *ptr1 = ptr + 1;
free(ptr - 1);
Did you mean to write
free(ptr1 - 1);
On Thu, 09 Jan 2025 23:40:52 -0800
Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
Andrey Tarasevich <andreytarasevich@hotmail.com> writes:
On 01/09/25 12:12 AM, Julio Di Egidio wrote:
I do not understand that: `free` is changing the pointed data, so
how can `const void *` even be "correct"?
`free` is destroying the pointed data.
Right. In other words, it causes the pointed-to data to reach the end
of its lifetime. "Changing" the data generally means modifying its
value (that's what "const" forbids).
Given:
int *ptr = malloc(sizeof *ptr);
*ptr = 42;
printf("*ptr = %d\n", *ptr);
free(ptr);
After the call to free(), the int object logically no longer exists.
Also, the value of the pointer object ptr becomes indeterminate.
Attempting to refer to the value of either ptr or *ptr has undefined
behavior.
I believe that the Standard really says that, but find the part about
value of ptr variable ridiculous. It breaks natural hierarchy by which standard library is somewhat special, but it is not above rules of core language.
On 08/01/2025 17:48, Andrey Tarasevich wrote:
It is perfectly safe. One can even argue that standard declaration if
`free` as `void free(void *)` is defective. It should have been `void
free(const void *)` from the very beginning.
It is common in simple heap implementations for the allocated block to contain data about the block, such as allocation sizes and pointers to
other blocks, in memory just below the address returned by malloc.
free() then uses its parameter to access that data, and may change it.
So "void free(const void *);" would be lying to the user.
On 2025-01-10, Michael S <already5chosen@yahoo.com> wrote:...
On Thu, 09 Jan 2025 23:40:52 -0800
Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
int *ptr = malloc(sizeof *ptr);
*ptr = 42;
printf("*ptr = %d\n", *ptr);
free(ptr);
After the call to free(), the int object logically no longer exists.
Also, the value of the pointer object ptr becomes indeterminate.
Attempting to refer to the value of either ptr or *ptr has undefined
behavior.
I believe that the Standard really says that, but find the part about
value of ptr variable ridiculous. It breaks natural hierarchy by which
standard library is somewhat special, but it is not above rules of core
language.
The library is above the rules because it is not required to be
written in strictly conforming ISO C.
Andrey Tarasevich <andreytarasevich@hotmail.com> writes:
On 01/09/25 12:12 AM, Julio Di Egidio wrote:
I do not understand that: `free` is changing the pointed data, so
how can `const void *` even be "correct"?
`free` is destroying the pointed data.
Right. In other words, it causes the pointed-to data to reach the end
of its lifetime. "Changing" the data generally means modifying its
value (that's what "const" forbids).
Given:
int *ptr = malloc(sizeof *ptr);
*ptr = 42;
printf("*ptr = %d\n", *ptr);
free(ptr);
After the call to free(), the int object logically no longer exists.
Also, the value of the pointer object ptr becomes indeterminate.
Attempting to refer to the value of either ptr or *ptr has undefined behavior.
David Brown <david.brown@hesbynett.no> writes:
[...]
If you want a better signature for "free", then I would suggest "void
free(void ** p)" - that (to me) more naturally shows that the function
is freeing the pointer, while also greatly reducing the "use after
free" errors in C code by turning them into "dereferencing a null
pointer" errors which are more easily caught by many OS's.
I'm not sure that would work. A void** argument means you need to pass
a pointer to a void* object. If you've assigned the converted result of malloc() to, say, an int* object, you don't have a void* object. (int*
and void* might not even have the same representation).
Some kind of generic function that takes a pointer to an object of any
object pointer type could work, but the language doesn't support that.
(C++ addressed this by making `new` and `delete` built-in operators
rather than library functions.)
On 1/9/2025 11:40 PM, Keith Thompson wrote:
Andrey Tarasevich <andreytarasevich@hotmail.com> writes:
On 01/09/25 12:12 AM, Julio Di Egidio wrote:
I do not understand that: `free` is changing the pointed data, so
how can `const void *` even be "correct"?
`free` is destroying the pointed data.
Right. In other words, it causes the pointed-to data to reach the end
of its lifetime. "Changing" the data generally means modifying its
value (that's what "const" forbids).
Given:
int *ptr = malloc(sizeof *ptr);
*ptr = 42;
printf("*ptr = %d\n", *ptr);
free(ptr);
After the call to free(), the int object logically no longer exists.
Also, the value of the pointer object ptr becomes indeterminate.
Attempting to refer to the value of either ptr or *ptr has undefined
behavior.
I must be missing something here. Humm... I thought is was okay to do something like this:
_____________________________
#include <stdio.h>
#include <stdlib.h>
int main() {
int* a = malloc(sizeof(*a));
if (a)
{
*a = 42;
printf("a = %p\n", (void*)a);
printf("*a = %d\n", *a);
free(a);
printf("a = %p was just freed! do not deref\n", (void*)a);
}
return 0;
}
_____________________________
Is that okay?
[...]
David Brown <david.brown@hesbynett.no> writes:
[...]
If you want a better signature for "free", then I would suggest "void
free(void ** p)" - that (to me) more naturally shows that the function
is freeing the pointer, while also greatly reducing the "use after
free" errors in C code by turning them into "dereferencing a null
pointer" errors which are more easily caught by many OS's.
I'm not sure that would work. A void** argument means you need to pass
a pointer to a void* object. If you've assigned the converted result of malloc() to, say, an int* object, you don't have a void* object. (int*
and void* might not even have the same representation).
On 10/01/2025 19:56, Keith Thompson wrote:<snip>
The idea was to place the emphasis on "free" changing the pointer,
rather than the data pointed to.
On 11/01/2025 12:14, David Brown wrote:
On 10/01/2025 19:56, Keith Thompson wrote:<snip>
The idea was to place the emphasis on "free" changing the pointer,
rather than the data pointed to.
I feel I am still altogether missing the point.
Is my understanding correct that when freeing a pointer: 1) the pointer value, i.e. the address it holds, does not change; OTOH, 2) the
pointed-to object does change, in the sense that it is marked unusable
(and, supposedly, made available to re-allocation)?
Moreover, while the pointer value has not changed, it is in fact changed
in the sense that it has become invalid, namely the pointer cannot be
used (validly dereferenced) anymore. Not just that, but *every* pointer
to the same object, i.e. holding the same address, has become invalid.
All that considered, how isn't `void free(void *p)`, i.e. with no const qualifiers anywhere, the only reasonable signature?
On 11/01/2025 17:07, Julio Di Egidio wrote:
On 11/01/2025 12:14, David Brown wrote:
On 10/01/2025 19:56, Keith Thompson wrote:<snip>
The idea was to place the emphasis on "free" changing the pointer,
rather than the data pointed to.
I feel I am still altogether missing the point.
Is my understanding correct that when freeing a pointer: 1) the
pointer value, i.e. the address it holds, does not change; OTOH, 2)
the pointed-to object does change, in the sense that it is marked
unusable (and, supposedly, made available to re-allocation)?
Moreover, while the pointer value has not changed, it is in fact
changed in the sense that it has become invalid, namely the pointer
cannot be used (validly dereferenced) anymore. Not just that, but
*every* pointer to the same object, i.e. holding the same address, has
become invalid.
All that considered, how isn't `void free(void *p)`, i.e. with no
const qualifiers anywhere, the only reasonable signature?
In fact, along that line, I could see one might insist that "strictly speaking, it should be `void free(void *const p)` because the pointer
value is not changed" (my considerations above are indeed more
"semantic"), OTOH, I just cannot see a case for `void free(void const
*p)`, not even strictly technically.
Destruction by malloc is modifying in any system that recycles the
memory for another allocation.
On 11/01/2025 12:14, David Brown wrote:
On 10/01/2025 19:56, Keith Thompson wrote:<snip>
The idea was to place the emphasis on "free" changing the pointer,
rather than the data pointed to.
I feel I am still altogether missing the point.
Is my understanding correct that when freeing a pointer: 1) the
pointer value, i.e. the address it holds, does not change; OTOH, 2)
the pointed-to object does change, in the sense that it is marked
unusable (and, supposedly, made available to re-allocation)?
Moreover, while the pointer value has not changed, it is in fact
changed in the sense that it has become invalid, namely the pointer
cannot be used (validly dereferenced) anymore. Not just that, but
*every* pointer to the same object, i.e. holding the same address, has
become invalid.
All that considered, how isn't `void free(void *p)`, i.e. with no
const qualifiers anywhere, the only reasonable signature?
... Not just that, but *every* pointer to the same object, i.e.
holding the same address, has become invalid.
In fact, along that line, I could see one might insist that "strictly speaking, it should be `void free(void *const p)
On 1/10/25 11:57 PM, Chris M. Thomasson wrote:
On 1/9/2025 11:40 PM, Keith Thompson wrote:
Andrey Tarasevich <andreytarasevich@hotmail.com> writes:
On 01/09/25 12:12 AM, Julio Di Egidio wrote:
I do not understand that: `free` is changing the pointed data, so
how can `const void *` even be "correct"?
`free` is destroying the pointed data.
Right. In other words, it causes the pointed-to data to reach the end
of its lifetime. "Changing" the data generally means modifying its
value (that's what "const" forbids).
Given:
int *ptr = malloc(sizeof *ptr);
*ptr = 42;
printf("*ptr = %d\n", *ptr);
free(ptr);
After the call to free(), the int object logically no longer exists.
Also, the value of the pointer object ptr becomes indeterminate.
Attempting to refer to the value of either ptr or *ptr has undefined
behavior.
I must be missing something here. Humm... I thought is was okay to do
something like this:
_____________________________
#include <stdio.h>
#include <stdlib.h>
int main() {
int* a = malloc(sizeof(*a));
if (a)
{
*a = 42;
printf("a = %p\n", (void*)a);
printf("*a = %d\n", *a);
free(a);
printf("a = %p was just freed! do not deref\n", (void*)a); >> }
return 0;
}
_____________________________
Is that okay?
[...]
No, because the value of a has become indeterminate, and operating on
it, even to just look at its value, can trap.
you could save a representation of it either in a char array or as a uintptr_t value, and work with that (but not try to recreate a pointer
with it, as that pointer "value" has become indeterminate).
This issue CAN occur if the implementation is using segment_tag + offset pointers, and free invalidates the segment_tag of that the pointer used,
and the implementation will perhaps validate the segment_tag when
looking at the pointer value. (perhaps pointers are loaded into
registers that automatically validate the segment_tag in them).
On 01/10/25 10:37 AM, Kaz Kylheku wrote:
Destruction by malloc is modifying in any system that recycles the
memory for another allocation.
From such a radically "physical" point of view, nothing is and nothing
will ever be `const`... Sorry, this is not even close to what
"constness" in C is about.
In C and C++ (as well in virtually all higher level languages)
"constness" is not a physical concept.
It is a purely high-level
logic-level concept, designed, implemented and enforced entirely by the author of the code (of the public interface of the module) in accordance with their intent.
Julio Di Egidio <julio@diegidio.name> writes:
On 11/01/2025 12:14, David Brown wrote:
On 10/01/2025 19:56, Keith Thompson wrote:<snip>
The idea was to place the emphasis on "free" changing the pointer,
rather than the data pointed to.
I feel I am still altogether missing the point.
Is my understanding correct that when freeing a pointer: 1) the
pointer value, i.e. the address it holds, does not change; OTOH, 2)
the pointed-to object does change, in the sense that it is marked
unusable (and, supposedly, made available to re-allocation)?
Moreover, while the pointer value has not changed, it is in fact
changed in the sense that it has become invalid, namely the pointer
cannot be used (validly dereferenced) anymore. Not just that, but
*every* pointer to the same object, i.e. holding the same address, has
become invalid.
All that considered, how isn't `void free(void *p)`, i.e. with no
const qualifiers anywhere, the only reasonable signature?
It is.
The current declaration `void free(void *p)` implies that the free
function does not promise to modify the data pointed to by p. In fact
it may or may not do so. (No program can tell whether the data is
modified without undefined behavior.)
`void free(const void *p)` would imply a promise by free that it will
not modify that data. Since it does so in some implementations and
since any attempt to access that data would have undefined behavior,
that would not be useful.
In `void free(void *const p)`, the "const" would be nearly meaningless.
p is a parameter, a local object in the implementation of free. The
"const", if it appears in the definition (assuming free() is implemented
as a C function), would be a promise not to modify that local object -- something that should be of no relevance to callers. In the
declaration, it means nothing.
The visible declaration of free does not, and cannot, communicate all
the relevant information about it. The allocated data reaches the end
of its lifetime, and the pointer value passed to free therefore becomes indeterminate. The same thing happens with a pointer to a local object
when that object reaches the end of its lifetime:
{
int *p;
{
int n = 42;
p = &n;
}
printf("p = %p\n", (void*)p); // undefined behavior
}
C's declaration syntax can't express those effects, which is why they're stated explicitly in the standard.
On 2025-01-11, Andrey Tarasevich <andreytarasevich@hotmail.com> wrote:[...]
In C and C++ (as well in virtually all higher level languages)
"constness" is not a physical concept.
It is for defined objects. A const object of static duration can be
put into ROM, or virtual memory configured read-only. Either of these
can generate a trap if written.
On 1/10/25 05:23, Michael S wrote:
On Thu, 09 Jan 2025 23:40:52 -0800...
Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
After the call to free(), the int object logically no longer
exists. Also, the value of the pointer object ptr becomes
indeterminate. Attempting to refer to the value of either ptr or
*ptr has undefined behavior.
I believe that the Standard really says that, but find the part
about value of ptr variable ridiculous. It breaks natural hierarchy
by which standard library is somewhat special, but it is not above
rules of core language. free() is defined as function rather than
macro. By rules of core language, a function call can not modify
the value of local variable at caller's scope, unless pointers to
the variable was passed to it explicitly.
And that applies to free() just as much as any user-defined function.
Keep in mind that if p is a value returned by a call to a memory
management function (alloc_aligned(), malloc(), calloc() or
realloc()), the values of all pointers anywhere in the program that
point at any location in the block of memory allocated by the call to
that memory management function become indeterminate at the same
time. This doesn't mean that free() has permission to change the bit
pattern stored in any of those pointer objects. It doesn't. There's
no legal way to access the values stored in those objects; the only
thing you can do with those objects as a whole is to store a new
value in them. It is, however, legal to access the individual bytes
that make up the pointer's representation. Those bytes are themselves objects, and changing the values stored in those bytes would
therefore violate 6.2.4p2: "An object exists, has a constant
address36) , and retains its last-stored value throughout its
lifetime."
What the indeterminate value of those pointers does mean is that implementations have permission to remove that entire block of memory
from the list of valid pointer locations. On a system which uses
memory maps, that can be as simple as modifying one entry in the
memory map table.
By the way, this argument was controversial when I made it in a
discussion on this newsgroup in 2003 in a thread titled "pointer after
free indeterminate (your example James)". Yes, I am the "James" in
that title. You can look up that thread using Google Groups, if you
want to examine the arguments for and against. I don't believe that
there's been any relevant changes in the standard, but it's been two
decades and several revisions of the standard, so I could be wrong
about that.
Tried to read the old discussion. Didn't understand much.
But it strengthened my opinion: it's ridiculous. Standard would be way
better with this nonsense removed.
The function below should be guaranteed by standard to return 42.
int foo(void)
{
char* a = malloc(100);
if (!a) return 42;
char* b = a + 42;
free(a);
return b - a;
}
On 2025-01-09, David Brown <david.brown@hesbynett.no> wrote:
On 08/01/2025 17:48, Andrey Tarasevich wrote:
It is perfectly safe. One can even argue that standard declaration if
`free` as `void free(void *)` is defective. It should have been `void
free(const void *)` from the very beginning.
It is common in simple heap implementations for the allocated block to
contain data about the block, such as allocation sizes and pointers to
other blocks, in memory just below the address returned by malloc.
free() then uses its parameter to access that data, and may change it.
So "void free(const void *);" would be lying to the user.
If the pointer came from the allocator, as required, there is no lie.
The object is writable, and so free may strip away the qualifier
and do whatever it wants, like cover the object with 0xFE bytes.
Michael S <already5chosen@yahoo.com> writes:
[...]
Tried to read the old discussion. Didn't understand much.
But it strengthened my opinion: it's ridiculous. Standard would be way
better with this nonsense removed.
The function below should be guaranteed by standard to return 42.
int foo(void)
{
char* a = malloc(100);
if (!a) return 42;
char* b = a + 42;
free(a);
return b - a;
}
How exactly would that be useful?
The intent, I think, is to allow for implementations that perform
checking when loading a pointer value. It's possible that there
currently are no such implementations, but even so, how would
reading a free()d pointer value be useful?
I'd say that passing a pointer value to free() means you no longer care
about it.
Julio Di Egidio <julio@diegidio.name> writes:
On 09/01/2025 02:09, Ben Bacarisse wrote:
Julio Di Egidio <julio@diegidio.name> writes:
static AvlTree_t const *AvlTree_node(Just on a side issue, I prefer to make tests like this positive so I'd
void const *pk, AvlTree_t const *pL, AvlTree_t const *pR
) {
AvlTree_t *pT;
pT = malloc(sizeof(AvlTree_t));
if (!pT) {
return NULL;
}
pT->pk = pk;
pT->pL = pL;
pT->pR = pR;
return pT;
}
write:
static AvlTree_t const *AvlTree_node(
void const *pk, AvlTree_t const *pL, AvlTree_t const *pR
) {
AvlTree_t *pT = malloc(*pT);
if (pT) {
pT->pk = pk;
pT->pL = pL;
pT->pR = pR;
}
return pT;
}
I'm not going to "make a case" for this (though I will if you want!) --
I just think it helps to see lots of different styles.
That is *more* error prone,
I would be happy for you to expand on why you say that.
Ben Bacarisse <ben@bsb.me.uk> wrote:
Julio Di Egidio <julio@diegidio.name> writes:
I would be happy for you to expand on why you say that.
Julio did not want to give his reasons,
On 11/01/2025 12:14, David Brown wrote:
On 10/01/2025 19:56, Keith Thompson wrote:
<snip>
The idea was to place the emphasis on "free" changing the pointer,
rather than the data pointed to.
I feel I am still altogether missing the point.
Is my understanding correct that when freeing a pointer: 1) the
pointer value, i.e. the address it holds, does not change; OTOH, 2)
the pointed-to object does change, in the sense that it is marked
unusable (and, supposedly, made available to re-allocation)?
Moreover, while the pointer value has not changed, it is in fact
changed in the sense that it has become invalid, namely the pointer
cannot be used (validly dereferenced) anymore. Not just that, but
*every* pointer to the same object, i.e. holding the same address, has
become invalid.
Hi everybody,
I am back to programming in C after many years:
indeed I have forgotten so many things, including
how much I love this language. :)
All the standard really says about "const" is that directly modifying
a const-qualified object is a constraint violation, and indirectly
modifying it has undefined behavior. [...]
On 10/01/2025 03:14, Julio Di Egidio wrote:...
...Julio Di Egidio <julio@diegidio.name> writes:
On 09/01/2025 02:09, Ben Bacarisse wrote:
Julio Di Egidio <julio@diegidio.name> writes:
...static AvlTree_t const *AvlTree_node(That is *more* error prone,
void const *pk, AvlTree_t const *pL, AvlTree_t const *pR
) {
AvlTree_t *pT = malloc(*pT);
if (pT) {
pT->pk = pk;
pT->pL = pL;
pT->pR = pR;
}
return pT;
}
I'm not going to "make a case" for this (though I will if you
want!) -- I just think it helps to see lots of different styles. >>>>>>>
... check the return value as soon as the function returns a possibly
null pointer or an error value is certainly more widely applicable,
and quite less error prone, especially if it's
I meant: immediately check the return value and bail out if needed.
The other approach does not even simplify on the clean-up, by the way...
On 01/10/25 10:37 AM, Kaz Kylheku wrote:
Destruction by malloc is modifying in any system that recycles the
memory for another allocation.
From such a radically "physical" point of view, nothing is and nothing
will ever be `const`... Sorry, this is not even close to what
"constness" in C is about.
In C and C++ (as well in virtually all higher level languages)
"constness" is not a physical concept. It is a purely high-level
logic-level concept, designed, implemented and enforced entirely by
the author of the code (of the public interface of the module) in
accordance with their intent.
It has absolutely no relation to any physical modifications that might
occur anywhere in the execution environment. Nobody cares whether
something somewhere gets "modified". It is always a question of
whether _I_ want to recognize such modifications as part of the public interface designed by me. I'm the one who says whether the operation
is "constant" or not, based purely on my idea of "logical constness".
That's the reason `const` exists in C (and C++).
However (returning to the more narrowly focused matter at hand), two
things - creation and deletion of objects - will always indisputably
stand apart as operations that transcend/defeat/ignore the idea of "constness" with relation to the object itself. Creation/deletion
might logically be seen as "non-constant" wrt to the surrounding
environment (e.g. memory manager), but wrt to the object itself they
shall not (and, obviously, cannot) care about its "constness" at all.
An object begins being `const` only after the process of its creation (construction) is complete. [...]
On 10/01/2025 02:43, Tim Rentsch wrote:
Julio Di Egidio <julio@diegidio.name> writes:
On 10/01/2025 00:37, Julio Di Egidio wrote:
On 10/01/2025 00:23, Ben Bacarisse wrote:
Julio Di Egidio <julio@diegidio.name> writes:
On 09/01/2025 02:09, Ben Bacarisse wrote:
Julio Di Egidio <julio@diegidio.name> writes:
static AvlTree_t const *AvlTree_node(
void const *pk, AvlTree_t const *pL, AvlTree_t const *pR
) {
AvlTree_t *pT;
pT = malloc(sizeof(AvlTree_t));
if (!pT) {
return NULL;
}
pT->pk = pk;
pT->pL = pL;
pT->pR = pR;
return pT;
}
Just on a side issue, I prefer to make tests like this positive
so I'd write:
static AvlTree_t const *AvlTree_node(
void const *pk, AvlTree_t const *pL, AvlTree_t const *pR
) {
AvlTree_t *pT = malloc(*pT);
if (pT) {
pT->pk = pk;
pT->pL = pL;
pT->pR = pR;
}
return pT;
}
I'm not going to "make a case" for this (though I will if you
want!) -- I just think it helps to see lots of different styles.
That is *more* error prone,
I would be happy for you to expand on why you say that.
all the more so if it's not a 5 liner...
There is no such thing as expanding 40 years of professional
experience in software engineering and programming and doing it
properly since day one: just think about that code and what I said
for what it's worth, in particular I haven't mentioned 5 liners by
chance, things are quite more complicated not in vitro.
And please do not hold a grudge about that: it's not me who was
trying to say how to write code... ;)
BTW, I hadn't mention it, but have you noticed the second one is
misindented? Between me and you, I can tell how long a piece of
code will take to break when in production by just looking at
it... A lot of fun. :)
The indentation was correct in Ben's original posting.
The misindentation first appeared in your followup to that
posting, where the quoted portion had been changed to remove a
blank line and over-indent the if().
But indeed the point is what happens in the long run: if you look
above mine is still better indented... :) But of course it is not indentation per se the problem: for example check the return value as
soon as the function returns a possibly null pointer or an error value
is certainly more widely applicable, and quite less error prone,
especially if it's not a 5 liner...
Anyway, I also truly believe
there is no point in belabouring the point: [...]
On 12/01/2025 11:22, Keith Thompson wrote:
Michael S <already5chosen@yahoo.com> writes:
[...]
Tried to read the old discussion. Didn't understand much.
But it strengthened my opinion: it's ridiculous. Standard would be way >>> better with this nonsense removed.
The function below should be guaranteed by standard to return 42.
int foo(void)
{
char* a = malloc(100);
if (!a) return 42;
char* b = a + 42;
free(a);
return b - a;
}
How exactly would that be useful?
The intent, I think, is to allow for implementations that perform
checking when loading a pointer value. It's possible that there
currently are no such implementations, but even so, how would
reading a free()d pointer value be useful?
I'd say that passing a pointer value to free() means you no longer care
about it.
What if, after the free(a), you have ...
char *b = malloc(100);
... and the old address of 'a' gets reused.
Could you then carry on using 'a'?
You'd be relying on chance, and it'd be terribly bad form ... but
would it be legal?
Michael S <already5chosen@yahoo.com> writes:
[...]
Tried to read the old discussion. Didn't understand much.
But it strengthened my opinion: it's ridiculous. Standard would be way
better with this nonsense removed.
The function below should be guaranteed by standard to return 42.
int foo(void)
{
char* a = malloc(100);
if (!a) return 42;
char* b = a + 42;
free(a);
return b - a;
}
How exactly would that be useful?
The intent, I think, is to allow for implementations that perform
checking when loading a pointer value. [...]
On 1/9/25 21:51, Julio Di Egidio wrote:
On 10/01/2025 03:14, Julio Di Egidio wrote:...
...Julio Di Egidio <julio@diegidio.name> writes:
On 09/01/2025 02:09, Ben Bacarisse wrote:
Julio Di Egidio <julio@diegidio.name> writes:
...static AvlTree_t const *AvlTree_node(That is *more* error prone,
void const *pk, AvlTree_t const *pL, AvlTree_t const *pR >>>>>>>>> ) {
AvlTree_t *pT = malloc(*pT);
if (pT) {
pT->pk = pk;
pT->pL = pL;
pT->pR = pR;
}
return pT;
}
I'm not going to "make a case" for this (though I will if you >>>>>>>>> want!) -- I just think it helps to see lots of different styles. >>>>>>>>
... check the return value as soon as the function returns a possibly
null pointer or an error value is certainly more widely applicable,
and quite less error prone, especially if it's
I meant: immediately check the return value and bail out if needed.
The other approach does not even simplify on the clean-up, by the way...
The code you're criticizing as more error prone does check the return
value as soon as the function returns, and bails out if needed. It just
bails out through the missing else clause rather than from the if-clause.
It does requires code to be indented farther than the other approach. I
have avoided writing code like that for that reason, particularly when there's a lot of code inside the it statement's controlled blocks -but
not because it's error prone.
Ben Bacarisse <ben@bsb.me.uk> wrote:
Julio Di Egidio <julio@diegidio.name> writes:
On 09/01/2025 02:09, Ben Bacarisse wrote:
Julio Di Egidio <julio@diegidio.name> writes:
static AvlTree_t const *AvlTree_node(Just on a side issue, I prefer to make tests like this positive so I'd >>>> write:
void const *pk, AvlTree_t const *pL, AvlTree_t const *pR
) {
AvlTree_t *pT;
pT = malloc(sizeof(AvlTree_t));
if (!pT) {
return NULL;
}
pT->pk = pk;
pT->pL = pL;
pT->pR = pR;
return pT;
}
static AvlTree_t const *AvlTree_node(
void const *pk, AvlTree_t const *pL, AvlTree_t const *pR
) {
AvlTree_t *pT = malloc(*pT);
if (pT) {
pT->pk = pk;
pT->pL = pL;
pT->pR = pR;
}
return pT;
}
I'm not going to "make a case" for this (though I will if you want!) -- >>>> I just think it helps to see lots of different styles.
That is *more* error prone,
I would be happy for you to expand on why you say that.
Julio did not want to give his reasons, but I have reasons to
prefer "negative" version too. Namely, significant trouble
with malloc is ensuring correct handling of failing case.
Since this code is not excercised by normal execution, this
should be done by robust coding pattern. In particular,
it is easier to check correctness when handling code is
immediately after 'malloc' and the test. So
AvlTree_t *pT = malloc(*pT);
if (!pT) {
return pT;
}
...
makes quite visible that handling is there and it is rather
minimal (in particular caller have to cope with null pointer
return). That is easily lost in "posive" version, where
error handling may be far away from the allocation and test.
Basically, this is similar idea to "defer" discussed in
another thread, just in this case requires no language
extention and simply adopting appropriate coding style
gives the effect.
On 12/01/2025 23:29, Waldek Hebisch wrote:
Ben Bacarisse <ben@bsb.me.uk> wrote:
Julio Di Egidio <julio@diegidio.name> writes:
I would be happy for you to expand on why you say that.
Julio did not want to give his reasons,
I did give reasons, plenty actually: some people just cannot but keep pretending.
On 12/01/2025 23:45, Julio Di Egidio wrote:
On 12/01/2025 23:29, Waldek Hebisch wrote:
Ben Bacarisse <ben@bsb.me.uk> wrote:
Julio Di Egidio <julio@diegidio.name> writes:
I would be happy for you to expand on why you say that.
Julio did not want to give his reasons,
I did give reasons, plenty actually: some people just cannot but keep
pretending.
Could you try posting them again? I've looked through your posts here again, and I haven't seen any clear justification for why you think
Ben's alternative structure for the code is "more error-prone".
... check the return value as soon as the--- Synchronet 3.20a-Linux NewsLink 1.114
function returns a possibly null pointer or an error value is certainly
more widely applicable, and quite less error prone, ...
On 13/01/2025 04:10, James Kuyper wrote:
On 1/9/25 21:51, Julio Di Egidio wrote:
You can of course also argue that it is best to have the code ordered according to the normal flow of operations - error handling in the
middle of the normal code flow can make it hard to follow the algorithm
of a function. (C++ exceptions are an extreme version of this.) Some people might prefer a compromise:
On 13/01/2025 09:58, David Brown wrote:
On 13/01/2025 04:10, James Kuyper wrote:
On 1/9/25 21:51, Julio Di Egidio wrote:
You can of course also argue that it is best to have the code ordered
according to the normal flow of operations - error handling in the
middle of the normal code flow can make it hard to follow the
algorithm of a function. (C++ exceptions are an extreme version of
this.) Some people might prefer a compromise:
Some people are just wrong and not even honest at that.
**Please don't mind and don't feed the trolls**:
I had the misfortune of calling out the utter and ugly bullshit from
these characters in the past in other groups: this gang of nazi-retarded
and mostly academic spammers and polluters of all ponds just hates my
guts since then.
BTW, on that C++ vs C issue (you know who you are), I respect that but
I'd say it's wrong: C and C++ share nothing but some syntax, and one is high-level, not the other...
Thanks again and please use a kill-file: I do.
On 13/01/2025 14:23, Julio Di Egidio wrote:<snipped>
On 13/01/2025 09:58, David Brown wrote:
On 13/01/2025 04:10, James Kuyper wrote:
On 1/9/25 21:51, Julio Di Egidio wrote:
You can of course also argue that it is best to have the code ordered
according to the normal flow of operations - error handling in the
middle of the normal code flow can make it hard to follow the
algorithm of a function. (C++ exceptions are an extreme version of
this.) Some people might prefer a compromise:
Some people are just wrong and not even honest at that.
**Please don't mind and don't feed the trolls**:
There are a few trollish characters in this newsgroup, but I don't
believe I have seen any in this thread.
I had the misfortune of calling out the utter and ugly bullshit from
these characters in the past in other groups: this gang of
nazi-retarded and mostly academic spammers and polluters of all ponds
just hates my guts since then.
Perhaps you have mixed up threads in different newsgroups?
BTW, on that C++ vs C issue (you know who you are), I respect that but
I'd say it's wrong: C and C++ share nothing but some syntax, and one
is high-level, not the other...
While there is one person who posts primarily about C++ in this C group,
and that can be annoying, it is not unreasonable to make occasional references or comparisons to C++ precisely because they are strongly
related languages that share a great deal of overlap.
On Thu, 09 Jan 2025 23:40:52 -0800
Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
Andrey Tarasevich <andreytarasevich@hotmail.com> writes:
On 01/09/25 12:12 AM, Julio Di Egidio wrote:
I do not understand that: `free` is changing the pointed data, so
how can `const void *` even be "correct"?
`free` is destroying the pointed data.
Right. In other words, it causes the pointed-to data to reach the end
of its lifetime. "Changing" the data generally means modifying its
value (that's what "const" forbids).
Given:
int *ptr = malloc(sizeof *ptr);
*ptr = 42;
printf("*ptr = %d\n", *ptr);
free(ptr);
After the call to free(), the int object logically no longer exists.
Also, the value of the pointer object ptr becomes indeterminate.
Attempting to refer to the value of either ptr or *ptr has undefined
behavior.
I believe that the Standard really says that, but find the part about
value of ptr variable ridiculous. It breaks natural hierarchy by which standard library is somewhat special, but it is not above rules of core language. free() is defined as function rather than macro. By rules of
core language, a function call can not modify the value of local
variable at caller's scope, unless pointers to the variable was passed
to it explicitly.
On Fri, 10 Jan 2025 08:50:07 -0500
James Kuyper <jameskuyper@alumni.caltech.edu> wrote:
On 1/10/25 05:23, Michael S wrote:
On Thu, 09 Jan 2025 23:40:52 -0800
Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
...
After the call to free(), the int object logically no longer
exists. Also, the value of the pointer object ptr becomes
indeterminate. Attempting to refer to the value of either ptr or
*ptr has undefined behavior.
I believe that the Standard really says that, but find the part
about value of ptr variable ridiculous. It breaks natural hierarchy
by which standard library is somewhat special, but it is not above
rules of core language. free() is defined as function rather than
macro. By rules of core language, a function call can not modify
the value of local variable at caller's scope, unless pointers to
the variable was passed to it explicitly.
And that applies to free() just as much as any user-defined function.
Keep in mind that if p is a value returned by a call to a memory
management function (alloc_aligned(), malloc(), calloc() or
realloc()), the values of all pointers anywhere in the program that
point at any location in the block of memory allocated by the call to
that memory management function become indeterminate at the same
time. This doesn't mean that free() has permission to change the bit
pattern stored in any of those pointer objects. It doesn't. There's
no legal way to access the values stored in those objects; the only
thing you can do with those objects as a whole is to store a new
value in them. It is, however, legal to access the individual bytes
that make up the pointer's representation. Those bytes are themselves
objects, and changing the values stored in those bytes would
therefore violate 6.2.4p2: "An object exists, has a constant
address36) , and retains its last-stored value throughout its
lifetime."
What the indeterminate value of those pointers does mean is that
implementations have permission to remove that entire block of memory
from the list of valid pointer locations. On a system which uses
memory maps, that can be as simple as modifying one entry in the
memory map table.
By the way, this argument was controversial when I made it in a
discussion on this newsgroup in 2003 in a thread titled "pointer after
free indeterminate (your example James)". Yes, I am the "James" in
that title. You can look up that thread using Google Groups, if you
want to examine the arguments for and against. I don't believe that
there's been any relevant changes in the standard, but it's been two
decades and several revisions of the standard, so I could be wrong
about that.
Tried to read the old discussion. Didn't understand much.
But it strengthened my opinion: it's ridiculous. Standard would be way better with this nonsense removed.
The function below should be guaranteed by standard to return 42.
int foo(void)
{
char* a = malloc(100);
if (!a) return 42;
char* b = a + 42;
free(a);
return b - a;
}
On 13/01/2025 17:30, David Brown wrote:
On 13/01/2025 14:23, Julio Di Egidio wrote:<snipped>
On 13/01/2025 09:58, David Brown wrote:
On 13/01/2025 04:10, James Kuyper wrote:
On 1/9/25 21:51, Julio Di Egidio wrote:
You can of course also argue that it is best to have the code
ordered according to the normal flow of operations - error handling
in the middle of the normal code flow can make it hard to follow the
algorithm of a function. (C++ exceptions are an extreme version of
this.) Some people might prefer a compromise:
Some people are just wrong and not even honest at that.
**Please don't mind and don't feed the trolls**:
There are a few trollish characters in this newsgroup, but I don't
believe I have seen any in this thread.
Then, with all due respect, you are being naive: "let's also not forget
to tell the OP how to write code" is a piece of it, for example.
But of
course from an impolite and vile request (reread how I was asked
initially) to me being the impolite one: it happens all the time
everywhere, I don't even mind anymore.
Nor it can become my problem that people can hardly ready, indeed literalism, mangling quotes especially never going past a formal level
that misses all that is substantial... which is where I said not all
code paths are ifs: only formally those two pieces of code are equivalent.
In fact my explanations go quite past what is just obvious: what does it even mean "in the long run?", or that formal is just half of the story
and not even the most relevant, more relevant is the concrete substance...? And should I just repeat myself? Really? For that questioning??
I had the misfortune of calling out the utter and ugly bullshit from
these characters in the past in other groups: this gang of
nazi-retarded and mostly academic spammers and polluters of all ponds
just hates my guts since then.
Perhaps you have mixed up threads in different newsgroups?
Or perhaps you just don't know and in fact don't need to know all the
gory details and pasty history. I am sorry that I even have to explain,
to you, who have been kind, polite, professional, to the point, helpful
and competently so, etc.: and not just you of course.
BTW, on that C++ vs C issue (you know who you are), I respect that
but I'd say it's wrong: C and C++ share nothing but some syntax, and
one is high-level, not the other...
While there is one person who posts primarily about C++ in this C
group, and that can be annoying, it is not unreasonable to make
occasional references or comparisons to C++ precisely because they are
strongly related languages that share a great deal of overlap.
There is a discussion going on and not only in this thread about e.g. "destructors" and what that means: of course I am referring to that and nothing but that. And there is nothing I need to add to that either:
those who know already know (what I mean), and those who don't, please
at least learn how to use question marks instead of just pretending. Mileages may very: your impoliteness or worse I won't take lightly.
And I hope that at least makes sense, and again I apologize to the
innocent and well intentioned, but what can we do, this is the world we
live in.... Anyway, of course, you be the judge. I am out, indeed I am here for the C language, not for anything else: and as far as I am
concerned this thread (surely my questions) is done.
There is an important distinction here, one I suspect you are either
glossing over or missing. The bits in the pointer don't change, but
the meaning of those bits can change. To say that a different way,
what memory location a pointer object representation (the bits)
designates can depend on information outside the pointer object
itself, depending on the machine architecture. A natural example is
a machine with segmented memory, where "a pointer" consists of a
segment number and an offset within the segment. If a free() has
been done by marking a particular segment invalid (such as by
setting a bit in a global segment table), the bits in the pointer
passed to free() don't change, but those bits no longer refer to any
memory location. The pointer becomes "indeterminate" even though
none of the bits in the pointer have changed. Does that make sense?
On 13/01/2025 20:30, Julio Di Egidio wrote:
On 13/01/2025 17:30, David Brown wrote:
On 13/01/2025 14:23, Julio Di Egidio wrote:<snipped>
On 13/01/2025 09:58, David Brown wrote:
On 13/01/2025 04:10, James Kuyper wrote:
On 1/9/25 21:51, Julio Di Egidio wrote:
You can of course also argue that it is best to have the code
ordered according to the normal flow of operations - error handling >>>>> in the middle of the normal code flow can make it hard to follow
the algorithm of a function. (C++ exceptions are an extreme
version of this.) Some people might prefer a compromise:
Some people are just wrong and not even honest at that.
**Please don't mind and don't feed the trolls**:
There are a few trollish characters in this newsgroup, but I don't
believe I have seen any in this thread.
Then, with all due respect, you are being naive: "let's also not
forget to tell the OP how to write code" is a piece of it, for example.
Could you point to the post from which you are quoting?
It is very difficult to figure out what you are trying to say here.
Let me try to rephrase it, before addressing it. If you don't think
my rephrasing is accurate, then my response to it is probably not
relevant and should be ignored.
If that is all the explanation we will get for your outburst, then
let's leave it there. But no, you are not making sense at all.
Stupid ignorant deadly and especially vile pieces of shit, the colonists...
Eat my socks and die.
*Plonk*
On 14/01/2025 08:02, Julio Di Egidio wrote:
Stupid ignorant deadly and especially vile pieces of shit, the
colonists...
Eat my socks and die.
*Plonk*
I think we just found the mysterious troll you were talking about.
On 13/01/2025 21:45, David Brown wrote:...
On 13/01/2025 20:30, Julio Di Egidio wrote:
Then, with all due respect, you are being naive: "let's also not
forget to tell the OP how to write code" is a piece of it, for example.
Could you point to the post from which you are quoting?
That was said to you by Ben Bacarisse at some point early in the thread.
I'm not sure if that beats being seriously accused of being possessed by
the devil (as I once was - I am sure you can guess who by).
On 1/14/25 08:50, David Brown wrote:
...
I'm not sure if that beats being seriously accused of being possessed by
the devil (as I once was - I am sure you can guess who by).
The oldest message I could find using Google Groups that contained
"David Brown", "possessed" and "devil" was a message posted by you on >2015-11-01 saying that Rick Hodgins had repeatedly told you that you
were possessed by the devil. It didn't show me any messages in which he >actually made that claim. I don't doubt you, but I'm curious about the >context in which he said that. Can you identify one such message?
On 1/14/25 08:50, David Brown wrote:
...
I'm not sure if that beats being seriously accused of being possessed by
the devil (as I once was - I am sure you can guess who by).
The oldest message I could find using Google Groups that contained
"David Brown", "possessed" and "devil" was a message posted by you on 2015-11-01 saying that Rick Hodgins had repeatedly told you that you
were possessed by the devil. It didn't show me any messages in which he actually made that claim. I don't doubt you, but I'm curious about the context in which he said that. Can you identify one such message?
Your blindness and insistence is yet another a piece of stupid and[...]
vile shit indeed, not just "irrelevant" or off mark, you other stupid
piece of nazi-retarded shit.
On 14/01/2025 13:44, bart wrote:
On 14/01/2025 08:02, Julio Di Egidio wrote:
Stupid ignorant deadly and especially vile pieces of shit, the
colonists...
Eat my socks and die.
*Plonk*
I think we just found the mysterious troll you were talking about.
I'm not sure if that beats being seriously accused of being possessed by
the devil (as I once was - I am sure you can guess who by).
But at
least that case took a long time to build up - this character went from
"you who have been kind, polite, professional, to the point, helpful" to
a foaming-at-the-mouth meltdown in just one post.
Ah well. I hope he finds some help somewhere for whatever his problem
is - he is certainly now unlikely to find much help on C around here!
On 14/01/2025 19:06, James Kuyper wrote:...
The oldest message I could find using Google Groups that contained
"David Brown", "possessed" and "devil" was a message posted by you on
2015-11-01 saying that Rick Hodgins had repeatedly told you that you
were possessed by the devil. It didn't show me any messages in which he
actually made that claim. I don't doubt you, but I'm curious about the
context in which he said that. Can you identify one such message?
I hadn't intended to go into detail about that - it was a throw-away
comment that I thought Bart might find amusing as it could bring back memories of some quite imaginative attacks. If you don't remember the
poster in question and some of the discussions back then, then I am
certainly not going to regurgitate them now.
On 1/14/2025 5:50 AM, David Brown wrote:
On 14/01/2025 13:44, bart wrote:
On 14/01/2025 08:02, Julio Di Egidio wrote:
Stupid ignorant deadly and especially vile pieces of shit, the
colonists...
Eat my socks and die.
*Plonk*
I think we just found the mysterious troll you were talking about.
I'm not sure if that beats being seriously accused of being possessed
by the devil (as I once was - I am sure you can guess who by).
The hard core religious guy? Iirc, Rick?
But at least that case took a long time to build up - this character
went from "you who have been kind, polite, professional, to the point,
helpful" to a foaming-at-the-mouth meltdown in just one post.
Ah well. I hope he finds some help somewhere for whatever his problem
is - he is certainly now unlikely to find much help on C around here!
On 14/01/2025 13:44, bart wrote:
On 14/01/2025 08:02, Julio Di Egidio wrote:
Stupid ignorant deadly and especially vile pieces of shit, the
colonists...
Eat my socks and die.
*Plonk*
I think we just found the mysterious troll you were talking about.
I'm not sure if that beats being seriously accused of being possessed by
the devil (as I once was - I am sure you can guess who by). But at
least that case took a long time to build up - this character went from
"you who have been kind, polite, professional, to the point, helpful" to
a foaming-at-the-mouth meltdown in just one post.
Ah well. I hope he finds some help somewhere for whatever his problem
is - he is certainly now unlikely to find much help on C around here!
On 14/01/2025 14:50, David Brown wrote:
On 14/01/2025 13:44, bart wrote:
On 14/01/2025 08:02, Julio Di Egidio wrote:
Stupid ignorant deadly and especially vile pieces of shit, the
colonists...
Eat my socks and die.
*Plonk*
I think we just found the mysterious troll you were talking about.
I'm not sure if that beats being seriously accused of being possessed
by the devil (as I once was - I am sure you can guess who by). But at
least that case took a long time to build up - this character went
from "you who have been kind, polite, professional, to the point,
helpful" to a foaming-at-the-mouth meltdown in just one post.
Ah well. I hope he finds some help somewhere for whatever his problem
is - he is certainly now unlikely to find much help on C around here!
You must mean some even more nazi-retarded lurid whit, don't you...
You ungrateful, impolite, not just fraudulent moron, I was trying to be nice: do you think those smilies to you since the very beginning were
per chance? And do you really think I am gonna use C99? Because you
said so?? And so competently??? LOL, I should have known better: not
to reply neither to you nor to anybody going even vaguely near that
PIECE OF polluting and fraudulent shit BEN BACARISSE
and his gang of retarded and fraudulent polluters of all ponds, of
course. But what can I do, I am a nice guy: I always give people a chance.
But, back to us, YOU piece of shit: the ONE crucial bottom question I
had for you, you have not even tried to approach: on a sub-thread that
YOU had started by the way, not me: as if I give a shit what C standard people suggest on the Internet, on Usenet of all things, in 2025 or
whether "what is the problem with malloc"? Really?? YOU must be
fucking out of your mind.
You, your friends, and the fucking fraudulent monsters: 10 mistakes
every 5 lines of code I call you. It's just fucking EMBARRASSING: and that's your legacy, you polluters of all ponds: that, climate change,
and the global shithole for everybody that is covering it all. Indeed
not me, your kids and grand-kids are gonna thank you so much.
But really, do you think I give a shit about "having conversations on Usenet"? About C? With people who cannot even read English?? You demented pieces of abusive crap: indeed, I rather hope the innocents meanwhile are having a fucking good laugh.
(Sure, you keep looking...)
*Plonk* (morons)
I certainly remember him, which is why I could easily believe that he
might accuse you of being possessed by the devil. However, I don't
remember him actually doing so. Since he spent a lot of time in my
killfile, that's not exactly unlikely.
moron, I was trying to be nice:
On 2025-01-15, Julio Di Egidio <julio@diegidio.name> wrote:
moron, I was trying to be nice:
I.e. you're not nice, but you try (good for you); but this is what
happens when it gets too much and you need a break from all that trying.
On 15/01/2025 04:12, Kaz Kylheku wrote:
On 2025-01-15, Julio Di Egidio <julio@diegidio.name> wrote:
moron, I was trying to be nice:
I.e. you're not nice, but you try (good for you); but this is what
happens when it gets too much and you need a break from all that trying.
Are you missing your share? You did help, and I said that much. I have not even commented on the article that brought me here to begin with. Is that not enough?
On 1/14/25 17:20, James Kuyper wrote:
...
I certainly remember him, which is why I could easily believe that he
might accuse you of being possessed by the devil. However, I don't
remember him actually doing so. Since he spent a lot of time in my
killfile, that's not exactly unlikely.
I had considerable trouble finding a message that I think is the basis
for what you said.
So ... the form of expressions allowed with “const” ... do we call them “const-able”?
Seems this term could have unfortunate implications if used carelessly ... wonder how you would police such a thing ...
On 15/01/2025 03:15, James Kuyper wrote:
On 1/14/25 17:20, James Kuyper wrote:
...
I certainly remember him, which is why I could easily believe that he
might accuse you of being possessed by the devil. However, I don't
remember him actually doing so. Since he spent a lot of time in my
killfile, that's not exactly unlikely.
I had considerable trouble finding a message that I think is the basis
for what you said.
James, I am sure you mean well, but I really don't think we need to drag this up here. I mentioned that old accusation as a humorous comparison
of absurd are over-the-top insults that I have seen over the years in Usenet. If I had imagined for a moment that someone would take it as a serious task to dig through archives and expand on it here in one of the rare topical threads in comp.lang.c, I would not have mentioned it.
Of course I am not the slightest bit bothered by either that old
accusation or Julio's current ones. And in both cases I do not think I wrote things that justified such reactions. (I /have/ written things in the past that have justified angry reactions - I am not always as polite
or considerate as I should be.)
So please forget that I ever brought that up, and drop this line of
posting here on Usenet, for the sake of everyone else. If you want to discuss it more, you know my email address is valid.
On 13/01/2025 20:30, Julio Di Egidio wrote:
On 13/01/2025 17:30, David Brown wrote:
On 13/01/2025 14:23, Julio Di Egidio wrote:<snipped>
On 13/01/2025 09:58, David Brown wrote:
On 13/01/2025 04:10, James Kuyper wrote:
On 1/9/25 21:51, Julio Di Egidio wrote:
You can of course also argue that it is best to have the code
ordered according to the normal flow of operations - error handling >>>>> in the middle of the normal code flow can make it hard to follow
the algorithm of a function. (C++ exceptions are an extreme
version of this.) Some people might prefer a compromise:
Some people are just wrong and not even honest at that.
**Please don't mind and don't feed the trolls**:
There are a few trollish characters in this newsgroup, but I don't
believe I have seen any in this thread.
Then, with all due respect, you are being naive: "let's also not
forget to tell the OP how to write code" is a piece of it, for example.
Could you point to the post from which you are quoting?
On 13/01/2025 14:23, Julio Di Egidio wrote:
On 13/01/2025 09:58, David Brown wrote:
On 13/01/2025 04:10, James Kuyper wrote:
On 1/9/25 21:51, Julio Di Egidio wrote:
You can of course also argue that it is best to have the code ordered
according to the normal flow of operations - error handling in the
middle of the normal code flow can make it hard to follow the
algorithm of a function. (C++ exceptions are an extreme version of
this.) Some people might prefer a compromise:
Some people are just wrong and not even honest at that.
**Please don't mind and don't feed the trolls**:
There are a few trollish characters in this newsgroup, but I don't
On 15/01/2025 03:09, Lawrence D'Oliveiro wrote:
So ... the form of expressions allowed with “const” ... do we call them >> “const-able”?
Seems this term could have unfortunate implications if used carelessly
...
wonder how you would police such a thing ...
I think we could merge in some ideas from INTERCAL and get a modern and
very safe programming language full of "PLEASE const-able" expressions!
:-)
<https://en.wikipedia.org/wiki/INTERCAL>
On 08/01/2025 12:25, Ben Bacarisse wrote:
David Brown <david.brown@hesbynett.no> writes:
Suppose you have :
int v = 123; // Non-const object definition
const int * cp = &v; // Const pointer to non-const data
int * p = (int *) cp; // Cast to non-const pointer
*p = 456; // Change the target data
This is allowed, because the original object definition was not a const
definition.
However, with this:
int v = 123; // Const object definition
Correction:
const int v = 123;
const int * cp = &v; // Const pointer to const data
int * p = (int *) cp; // Cast to non-const pointer
*p = 456; // Undefined behaviour
I think missed out the crucial "const" on the first line of the second
example! It's always the way.
Fortunately, Usenet is a self-correcting medium :-) Thanks for pointing out that mistake, and I hope the OP sees your correction before getting confused by my copy-pasta error.
You can make the pointer to non-const, but trying to change an object
that
was /defined/ as const is undefined behaviour (even if it was not
placed in
read-only memory).
When you use dynamic memory, however, you are not defining an object
in the
same way. If you write :
const int * cp = malloc(sizeof(int));
I prefer
const int *cp = malloc(sizeof *cp);
That's a common preference. Personally, I prefer the former - I think
it makes it clearer that we are allocating space for an int. Hopefully
the OP will hang around this group and we'll get a chance to give advice
and suggestions on many different aspects of C programming.
you are defining the object "p" as a pointer to type "const int" -
but you
are not defining a const int. You can cast "cp" to "int *" and use that >>> new pointer to change the value.
Julio Di Egidio <julio@diegidio.name> writes:
static AvlTree_t const *AvlTree_node(
void const *pk, AvlTree_t const *pL, AvlTree_t const *pR
) {
AvlTree_t *pT;
pT = malloc(sizeof(AvlTree_t));
if (!pT) {
return NULL;
}
pT->pk = pk;
pT->pL = pL;
pT->pR = pR;
return pT;
}
Just on a side issue, I prefer to make tests like this positive so I'd
write:
static AvlTree_t const *AvlTree_node(
void const *pk, AvlTree_t const *pL, AvlTree_t const *pR
) {
AvlTree_t *pT = malloc(*pT);
if (pT) {
pT->pk = pk;
pT->pL = pL;
pT->pR = pR;
}
return pT;
}
I'm not going to "make a case" for this (though I will if you want!) --
I just think it helps to see lots of different styles.
On 09/01/2025 05:24, Kaz Kylheku wrote:
On 2025-01-09, Ben Bacarisse <ben@bsb.me.uk> wrote:
Julio Di Egidio <julio@diegidio.name> writes:
static AvlTree_t const *AvlTree_node(
void const *pk, AvlTree_t const *pL, AvlTree_t const *pR
) {
AvlTree_t *pT;
pT = malloc(sizeof(AvlTree_t));
if (!pT) {
return NULL;
}
pT->pk = pk;
pT->pL = pL;
pT->pR = pR;
return pT;
}
Just on a side issue, I prefer to make tests like this positive so I'd
write:
static AvlTree_t const *AvlTree_node(
void const *pk, AvlTree_t const *pL, AvlTree_t const *pR
) {
AvlTree_t *pT = malloc(*pT);
if (pT) {
pT->pk = pk;
pT->pL = pL;
pT->pR = pR;
}
return pT;
}
More generally:
foo_handle *foo = foo_create();
bar_handle *bar = foo ? bar_create(foo) : 0; // doesn't like null
xyzzy_handle *xyz = xyzzy_create(42, bar, arg);
container *con = malloc(sizeof *con);
if (foo && bar && xyz && con) {
// happy case: we have all three resources
con->foo = foo;
con->bar = bar;
con->xyz = xyz;
return con;
}
xyzzy_destroy(xyz);
xyzzy_destroy(bar);
if (foo)
xyzzy_destroy(foo); // stupidly doesn't like null
return 0;
I'm not going to "make a case" for this (though I will if you want!) --
I just think it helps to see lots of different styles.
I might just have made the case. When more resources need to be
acquired that might fail, it consolidates the happy case under one
conjunctive test, and consolidates the cleanup in the unhappy case.
Effectively it's almost if we have only two cases.
A minor disadvantage is that in the unhappy flow, we may allocate
resources past the point where it is obvious they are not going to be
needed: if foo_create() failed, we are pointlessly calling
xyzzy_create() and malloc for the container. It's possible that these
succeed, and we are just going to turn around and free them.
How about taking the idea slightly further and making the later
allocations conditional too?
foo_handle *foo = foo_create();
bar_handle *bar = foo ? bar_create(foo) : 0; // doesn't like null
xyzzy_handle *xyz = bar ? xyzzy_create(42, bar, arg) : 0;
container *con = xyz ? malloc(sizeof *con) : 0;
if (con) {
// happy case: we have all three resources
...
If you are going to use that style (and I not arguing for or against
it), go all in!
It's a form of consolidated error checking, like when we make
several system calls and check them for errors as a batch;
e.g. call fprintf several times and check for disk full (etc)
just once.
Julio Di Egidio <julio@diegidio.name> writes:
On 09/01/2025 02:09, Ben Bacarisse wrote:
Julio Di Egidio <julio@diegidio.name> writes:
static AvlTree_t const *AvlTree_node(Just on a side issue, I prefer to make tests like this positive so I'd
void const *pk, AvlTree_t const *pL, AvlTree_t const *pR
) {
AvlTree_t *pT;
pT = malloc(sizeof(AvlTree_t));
if (!pT) {
return NULL;
}
pT->pk = pk;
pT->pL = pL;
pT->pR = pR;
return pT;
}
write:
static AvlTree_t const *AvlTree_node(
void const *pk, AvlTree_t const *pL, AvlTree_t const *pR
) {
AvlTree_t *pT = malloc(*pT);
if (pT) {
pT->pk = pk;
pT->pL = pL;
pT->pR = pR;
}
return pT;
}
I'm not going to "make a case" for this (though I will if you want!) --
I just think it helps to see lots of different styles.
That is *more* error prone,
I would be happy for you to expand on why you say that.
all the more so if it's not a 5 liner...
On 13/01/2025 04:10, James Kuyper wrote:
On 1/9/25 21:51, Julio Di Egidio wrote:
On 10/01/2025 03:14, Julio Di Egidio wrote:...
...Julio Di Egidio <julio@diegidio.name> writes:
On 09/01/2025 02:09, Ben Bacarisse wrote:
Julio Di Egidio <julio@diegidio.name> writes:
...static AvlTree_t const *AvlTree_node(That is *more* error prone,
void const *pk, AvlTree_t const *pL, AvlTree_t const *pR >>>>>>>>>> ) {
AvlTree_t *pT = malloc(*pT);
if (pT) {
pT->pk = pk;
pT->pL = pL;
pT->pR = pR;
}
return pT;
}
I'm not going to "make a case" for this (though I will if you >>>>>>>>>> want!) -- I just think it helps to see lots of different styles. >>>>>>>>>
... check the return value as soon as the function returns a possibly
null pointer or an error value is certainly more widely applicable,
and quite less error prone, especially if it's
I meant: immediately check the return value and bail out if needed.
The other approach does not even simplify on the clean-up, by the way...
The code you're criticizing as more error prone does check the return
value as soon as the function returns, and bails out if needed. It just
bails out through the missing else clause rather than from the if-clause.
It does requires code to be indented farther than the other approach. I
have avoided writing code like that for that reason, particularly when
there's a lot of code inside the it statement's controlled blocks -but
not because it's error prone.
I'm wary of assuming a particular interpretation of what someone else
wrote, but I would say there is a valid argument for say that the second style of code is more "error prone".
Different styles have different pros and cons, and different balances of emphasis. (They can also have different efficiencies in the normal and error cases, but that's usually less important.)
On 13/01/2025 09:58, David Brown wrote:
On 13/01/2025 04:10, James Kuyper wrote:
It does requires code to be indented farther than the other approach. I
have avoided writing code like that for that reason, particularly when
there's a lot of code inside the it statement's controlled blocks -but
not because it's error prone.
That's of course correct, what else.
Julio Di Egidio <julio@diegidio.name> writes:
On 10/01/2025 00:37, Julio Di Egidio wrote:
On 10/01/2025 00:23, Ben Bacarisse wrote:
Julio Di Egidio <julio@diegidio.name> writes:
On 09/01/2025 02:09, Ben Bacarisse wrote:
Julio Di Egidio <julio@diegidio.name> writes:
static AvlTree_t const *AvlTree_node(
void const *pk, AvlTree_t const *pL, AvlTree_t const *pR
) {
AvlTree_t *pT;
pT = malloc(sizeof(AvlTree_t));
if (!pT) {
return NULL;
}
pT->pk = pk;
pT->pL = pL;
pT->pR = pR;
return pT;
}
Just on a side issue, I prefer to make tests like this positive
so I'd write:
static AvlTree_t const *AvlTree_node(
void const *pk, AvlTree_t const *pL, AvlTree_t const *pR
) {
AvlTree_t *pT = malloc(*pT);
if (pT) {
pT->pk = pk;
pT->pL = pL;
pT->pR = pR;
}
return pT;
}
I'm not going to "make a case" for this (though I will if you
want!) -- I just think it helps to see lots of different styles.
That is *more* error prone,
I would be happy for you to expand on why you say that.
all the more so if it's not a 5 liner...
There is no such thing as expanding 40 years of professional
experience in software engineering and programming and doing it
properly since day one: just think about that code and what I said
for what it's worth, in particular I haven't mentioned 5 liners by
chance, things are quite more complicated not in vitro.
And please do not hold a grudge about that: it's not me who was
trying to say how to write code... ;)
BTW, I hadn't mention it, but have you noticed the second one is
misindented? Between me and you, I can tell how long a piece of
code will take to break when in production by just looking at
it... A lot of fun. :)
The indentation was correct in Ben's original posting.
The misindentation first appeared in your followup to that
posting, where the quoted portion had been changed to remove a
blank line and over-indent the if().
Julio Di Egidio <julio@diegidio.name> writes:
Overall, I am surmising this and only this might go write-protected:
MyStruct_t const T = {...};
Yes, though you should extend your concern beyond what might be
write-protected. Modifying an object whose type is const qualified is > undefined, even if the object is in writable storage. A compiler may
assume that such an object has not changed because in a program that has undefined behaviour, all bets are off. For example, under gcc with
almost any optimisation this program prints 42:
#include <stdio.h>
void f(const int *ip)
{
*(int *)ip = 0;
}
int main(void)
{
const int a = 42;
f(&a);
printf("%d\n", a);
}
While this one allocates a "byte-array", i.e. irrespective of how the
pointer we are assigning it is declared:
MyStruct_t const *pT = malloc(...);
Is my understanding (to that point) correct?
Technically you get an object with no effective type. David's reply
included some references to find out more about the effective type of an object, but it is safe to say that these only come into play if you are messing about with the way you access the allocated storage (for example accessing it as a MyStruct but then later as a floating point object).
More relevant to a discussion of const is to ask what you plan to do
with pT since you can't (without a cast) assign any useful value to the allocated object.
It is generally better to use a non const-qualified pointer for the allocation but, when using the pointer, to pass it to functions that use
the right type depending on whether they modify the pointed-to object or
not. For example:
MyStack *sp = malloc(*sp);
...
stack_push(sp, 99);
...
if (stack_empty(sp)) ...
...
stack_free(sp);
we would have
void stack_push(MyStack *sp, int v) { ... }
bool stack_empty(MyStack const *sp) { ... }
void stack_free(MyStack *sp) { ... }
Julio Di Egidio <julio@diegidio.name> writes:
static AvlTree_t const *AvlTree_node(
void const *pk, AvlTree_t const *pL, AvlTree_t const *pR
) {
AvlTree_t *pT;
pT = malloc(sizeof(AvlTree_t));
if (!pT) {
return NULL;
}
pT->pk = pk;
pT->pL = pL;
pT->pR = pR;
return pT;
}
Just on a side issue, I prefer to make tests like this positive so I'd
write:
static AvlTree_t const *AvlTree_node(
void const *pk, AvlTree_t const *pL, AvlTree_t const *pR
) {
AvlTree_t *pT = malloc(*pT);
if (pT) {
pT->pk = pk;
pT->pL = pL;
pT->pR = pR;
}
return pT;
}
I'm not going to "make a case" for this (though I will if you want!) --
I just think it helps to see lots of different styles.
On 1/9/25 21:51, Julio Di Egidio wrote:
On 10/01/2025 03:14, Julio Di Egidio wrote:...
...Julio Di Egidio <julio@diegidio.name> writes:
On 09/01/2025 02:09, Ben Bacarisse wrote:
Julio Di Egidio <julio@diegidio.name> writes:
...static AvlTree_t const *AvlTree_node(That is *more* error prone,
void const *pk, AvlTree_t const *pL, AvlTree_t const *pR >>>>>>>>> ) {
AvlTree_t *pT = malloc(*pT);
if (pT) {
pT->pk = pk;
pT->pL = pL;
pT->pR = pR;
}
return pT;
}
I'm not going to "make a case" for this (though I will if you >>>>>>>>> want!) -- I just think it helps to see lots of different styles. >>>>>>>>
... check the return value as soon as the function returns a possibly
null pointer or an error value is certainly more widely applicable,
and quite less error prone, especially if it's
I meant: immediately check the return value and bail out if needed.
The other approach does not even simplify on the clean-up, by the way...
The code you're criticizing as more error prone does check the return
value as soon as the function returns, and bails out if needed.It just
bails out through the missing else clause rather than from the if-clause.
It does requires code to be indented farther than the other approach. I
have avoided writing code like that for that reason, particularly when there's a lot of code inside the it statement's controlled blocks -but
not because it's error prone.
On 08/01/2025 13:25, David Brown wrote:
On 08/01/2025 12:25, Ben Bacarisse wrote:
David Brown <david.brown@hesbynett.no> writes:
Suppose you have :
int v = 123; // Non-const object definition
const int * cp = &v; // Const pointer to non-const data
int * p = (int *) cp; // Cast to non-const pointer
*p = 456; // Change the target data
This is allowed, because the original object definition was not a const >>>> definition.
However, with this:
int v = 123; // Const object definition
Correction:
const int v = 123;
const int * cp = &v; // Const pointer to const data
int * p = (int *) cp; // Cast to non-const pointer
*p = 456; // Undefined behaviour
I think missed out the crucial "const" on the first line of the second
example! It's always the way.
Fortunately, Usenet is a self-correcting medium :-) Thanks for
pointing out that mistake, and I hope the OP sees your correction
before getting confused by my copy-pasta error.
LOOK AT THESE TWO PIECES OF NAZI-RETARDED SPAMMING SHIT.
You can make the pointer to non-const, but trying to change an
object that
was /defined/ as const is undefined behaviour (even if it was not
placed in
read-only memory).
When you use dynamic memory, however, you are not defining an object
in the
same way. If you write :
const int * cp = malloc(sizeof(int));
I prefer
const int *cp = malloc(sizeof *cp);
That's a common preference. Personally, I prefer the former - I think
it makes it clearer that we are allocating space for an int.
Hopefully the OP will hang around this group and we'll get a chance to
give advice and suggestions on many different aspects of C programming.
you are defining the object "p" as a pointer to type "const int" -
but you
are not defining a const int. You can cast "cp" to "int *" and use
that
new pointer to change the value.
Julio Di Egidio <julio@diegidio.name> writes:
[...]
Your blindness and insistence is yet another a piece of stupid and[...]
vile shit indeed, not just "irrelevant" or off mark, you other stupid
piece of nazi-retarded shit.
*plonk*
In article <87zfjt8728.fsf@nosuchdomain.example.com>,
Keith Thompson <Keith.S.Thompson+u@gmail.com> wrote:
Julio Di Egidio <julio@diegidio.name> writes:
[...]
Your blindness and insistence is yet another a piece of stupid and[...]
vile shit indeed, not just "irrelevant" or off mark, you other stupid
piece of nazi-retarded shit.
*plonk*
Sounds like he's got your number, Keithy.
Pro-tip: Never announce that you are plonking someone (just do it); it is
bad form, and shows that the guy has gotten under your skin.
Could you point to the post from which you are quoting?
Anyway, I am just forwarding it to the Punisher, I am only here to have
a good look at the fraudulent then lurid shit by the/some? comp.lang.c regulars.
On 15/01/2025 09:39, David Brown wrote:
On 15/01/2025 03:09, Lawrence D'Oliveiro wrote:
So ... the form of expressions allowed with “const” ... do we call them >>> “const-able”?
Seems this term could have unfortunate implications if used
carelessly ...
wonder how you would police such a thing ...
I think we could merge in some ideas from INTERCAL and get a modern
and very safe programming language full of "PLEASE const-able"
expressions!
:-)
<https://en.wikipedia.org/wiki/INTERCAL>
STICK IT UP YOUR ASS you fucking nazi retard.
On 09/01/2025 02:09, Ben Bacarisse wrote:
Julio Di Egidio <julio@diegidio.name> writes:
static AvlTree_t const *AvlTree_node(
void const *pk, AvlTree_t const *pL, AvlTree_t const *pR
) {
AvlTree_t *pT;
pT = malloc(sizeof(AvlTree_t));
if (!pT) {
return NULL;
}
pT->pk = pk;
pT->pL = pL;
pT->pR = pR;
return pT;
}
Just on a side issue, I prefer to make tests like this positive so I'd
write:
Just on a side issue, I prefer you go eat shit and die.
static AvlTree_t const *AvlTree_node(
void const *pk, AvlTree_t const *pL, AvlTree_t const *pR
) {
AvlTree_t *pT = malloc(*pT);
if (pT) {
pT->pk = pk;
pT->pL = pL;
pT->pR = pR;
}
return pT;
}
I'm not going to "make a case" for this (though I will if you want!) --
I just think it helps to see lots of different styles.
No, you won't, your pal David Satan Brown is gonna do it for you...
On 10/01/2025 02:43, Tim Rentsch wrote:
Julio Di Egidio <julio@diegidio.name> writes:
On 10/01/2025 00:37, Julio Di Egidio wrote:
On 10/01/2025 00:23, Ben Bacarisse wrote:
Julio Di Egidio <julio@diegidio.name> writes:
On 09/01/2025 02:09, Ben Bacarisse wrote:
Julio Di Egidio <julio@diegidio.name> writes:
static AvlTree_t const *AvlTree_node(
void const *pk, AvlTree_t const *pL, AvlTree_t const *pR
) {
AvlTree_t *pT;
pT = malloc(sizeof(AvlTree_t));
if (!pT) {
return NULL;
}
pT->pk = pk;
pT->pL = pL;
pT->pR = pR;
return pT;
}
Just on a side issue, I prefer to make tests like this
positive so I'd write:
static AvlTree_t const *AvlTree_node(
void const *pk, AvlTree_t const *pL, AvlTree_t const *pR
) {
AvlTree_t *pT = malloc(*pT);
if (pT) {
pT->pk = pk;
pT->pL = pL;
pT->pR = pR;
}
return pT;
}
I'm not going to "make a case" for this (though I will if you
want!) -- I just think it helps to see lots of different
styles.
That is *more* error prone,
I would be happy for you to expand on why you say that.
all the more so if it's not a 5 liner...
There is no such thing as expanding 40 years of professional
experience in software engineering and programming and doing it
properly since day one: just think about that code and what I
said for what it's worth, in particular I haven't mentioned 5
liners by chance, things are quite more complicated not in vitro.
And please do not hold a grudge about that: it's not me who was
trying to say how to write code... ;)
BTW, I hadn't mention it, but have you noticed the second one is
misindented? Between me and you, I can tell how long a piece of
code will take to break when in production by just looking at
it... A lot of fun. :)
The indentation was correct in Ben's original posting.
The misindentation first appeared in your followup to that
posting, where the quoted portion had been changed to remove a
blank line and over-indent the if().
You other blithering idiot: Ben's initial code had *a missed blank
line*, [...]
Julio Di Egidio <julio@diegidio.name> writes:
On 10/01/2025 02:43, Tim Rentsch wrote:
Julio Di Egidio <julio@diegidio.name> writes:
BTW, I hadn't mention it, but have you noticed the second one is
misindented? Between me and you, I can tell how long a piece of
code will take to break when in production by just looking at
it... A lot of fun. :)
The indentation was correct in Ben's original posting.
The misindentation first appeared in your followup to that
posting, where the quoted portion had been changed to remove a
blank line and over-indent the if().
You other blithering idiot: Ben's initial code had *a missed blank
line*, [...]
I was responding only to the issue of misindentation.
On 18/01/2025 20:36, Tim Rentsch wrote:
Julio Di Egidio <julio@diegidio.name> writes:
On 10/01/2025 02:43, Tim Rentsch wrote:
Julio Di Egidio <julio@diegidio.name> writes:
BTW, I hadn't mention it, but have you noticed the second one is
misindented? Between me and you, I can tell how long a piece of
code will take to break when in production by just looking at
it... A lot of fun. :)
The indentation was correct in Ben's original posting.
The misindentation first appeared in your followup to that
posting, where the quoted portion had been changed to remove a
blank line and over-indent the if().
You other blithering idiot: Ben's initial code had *a missed blank
line*, [...]
I was responding only to the issue of misindentation.
Still rewriting history, you piece of shit? You and your fraudulent
pals have indeed been talking RETARDED CRAP all the way. -- Now snip it again...
Stupid fucking nazi-retarded pieces of ungodly shit. Eat shit and die.
Sysop: | DaiTengu |
---|---|
Location: | Appleton, WI |
Users: | 1,007 |
Nodes: | 10 (0 / 10) |
Uptime: | 196:44:37 |
Calls: | 13,143 |
Files: | 186,574 |
D/L today: |
510 files (113M bytes) |
Messages: | 3,310,136 |