Witch style (F1, F2 ..) better represents the guidelines of your code?
//The function f never returns nullptr
int * f();
void F1() {
int* p = f();
if (p == nullptr) {
assert(false);
return;
}
// using *p ...
}
void F2()
{
int* p = f();
assert(p != nullptr);
// using *p ...
}
void F3()
{
int* p = f();
// using *p ...
}
void F4()
{
int* p = f();
if (p) {
// using *p ...
}
}
The same question but for struct members
struct user {
//Name is required, cannot be null.
char * name;
};
void F1(struct user* user) {
if (user->name == nullptr) {
assert(false);
return;
}
// using user->name ...
}
void F2(struct user* user) {
assert(user->name != nullptr);
// using user->name ...
}
void F3(struct user* user) {
// using user->name ...
}
void F4(struct user* user) {
if (user->name) {
// using user->name ...
}
}
On 03/09/2024 10:16, David Brown wrote:
On 03/09/2024 14:22, Thiago Adams wrote:
I am of the opinion that if something is clearly specified, you make
sure it is true when you are responsible for it - and then you can
assume it is true when you use the results. It makes no sense to me
to do something, and then immediately check that it is done.
Whenever possible, these specifications should be in code, not
comments - using whatever tools and extra features you have available
for use. Macros and conditional compilation help make code more
portable, and can also let you turn compile-time assumptions into
run-time checks during debugging.
Yes, this contract "function don't returns null" is very straightforward
for the caller and for the implementer.
The contract implementation can be checked inside function
implementation. (One place to check the contract implementation)
The contract usage, is checked in each caller, but very straightforward.
On the other hand, struct members have much more places where the
contract can be broken. Each function that have a non const access to user->name could break the contract implementation.
The risk is much bigger compared with the return case.
So I think it may make sense to have redundant runtime checks, but it
must be clear the the "compile time contract" is not that one.
For instance:
The first sample my create confusion (is name optional?)
void f(struct user* user)
{
if (user->name && strcmp(user->name, "john") == 0)
{
//...
}
}
But :
void f(struct user* user)
{
assert(user->name);
if (user->name && strcmp(user->name, "john") == 0)
{
//...
}
}
would show redundancy but making clear the contract still "name should
not be null"
On 03/09/2024 11:10, David Brown wrote:
On 03/09/2024 15:33, Thiago Adams wrote:
On 03/09/2024 10:16, David Brown wrote:
On 03/09/2024 14:22, Thiago Adams wrote:
I am of the opinion that if something is clearly specified, you make
sure it is true when you are responsible for it - and then you can
assume it is true when you use the results. It makes no sense to me
to do something, and then immediately check that it is done.
Whenever possible, these specifications should be in code, not
comments - using whatever tools and extra features you have
available for use. Macros and conditional compilation help make code
more portable, and can also let you turn compile-time assumptions
into run-time checks during debugging.
Yes, this contract "function don't returns null" is very
straightforward for the caller and for the implementer.
The contract implementation can be checked inside function
implementation. (One place to check the contract implementation)
The contract usage, is checked in each caller, but very straightforward. >>>
On the other hand, struct members have much more places where the
contract can be broken. Each function that have a non const access to
user->name could break the contract implementation.
The risk is much bigger compared with the return case.
You are asking about "guidelines". Yes, it is possible for code to
break specifications. The guideline is "don't do that".
The guidelines are more about the usage of runtime checks, are they
required? optional? etc.. when to use etc...
If you are mixing untrusted code with trusted code, then you have to
check the incoming data just like you do with data from external
sources. But most functions are not at such boundaries.
yes ..agree.
So I think it may make sense to have redundant runtime checks, but it
must be clear the the "compile time contract" is not that one.
For instance:
The first sample my create confusion (is name optional?)
void f(struct user* user)
{
if (user->name && strcmp(user->name, "john") == 0)
{
//...
}
}
But :
void f(struct user* user)
{
assert(user->name);
if (user->name && strcmp(user->name, "john") == 0)
{
//...
}
}
would show redundancy but making clear the contract still "name
should not be null"
No, redundant runtime checks are not helpful. If they are redundant,
they are by definition a waste of effort - either run-time efficiency,
or developer efficiency, or both. And they are a maintenance mess as
changes have to be applied in multiple places.
But we know , when the code is new (under development) the contract may
not be so clear or change very frequently.
void f(struct user* user)
{
if (!user->name) __builtin_unreachable();
if (strcmp(user->name, "john") == 0) {
//...
}
}
Now it is /really/ clear that user->name should not be null, and
instead of two run-time checks doing the same thing, there are no
run-time costs and the compiler may even be able to optimise more from
the knowledge that the pointer is not null.
In practice I use a macro called "Assume" here, which will give a hard
compile-time error if the compiler can that the assumption is false.
It also supports optional run-time checks depending on a #define'd
flag (normally set as a -D compiler flag), as an aid to debugging.
Write things clearly, once, in code.
If the code is boundary code, then you need a check - but then a
simple null pointer check is unlikely to be sufficient. (What if the
pointer is not null, but not a valid pointer? Or it does not point to
a string of a suitable length, with suitable character set, etc.?)
The macro I need is something like
"I know this check is redundant, but I will add it anyway" "this check
is redundant but according with the contract"
assert/assume is more
"trust me compiler, this is what happens" someone else is checking this
we don't need to check again.
On 03/09/2024 10:33, Thiago Adams wrote:
...
For instance:
The first sample my create confusion (is name optional?)
void f(struct user* user)
{
if (user->name && strcmp(user->name, "john") == 0)
{
//...
}
}
But :
void f(struct user* user)
{
assert(user->name);
if (user->name && strcmp(user->name, "john") == 0)
{
//...
}
}
would show redundancy but making clear the contract still "name should
not be null"
Redundant code can either indicate a programmer's mental confusion
or
serve as a way to address potential contract violations.
I believe the objective is to ensure that runtime checks are not
questioning the contract but rather functioning as redundant safeguards.
In other words, the programmer must demonstrate that they understand the contract and are not messing it.
A safeguards for a very low risk situation also may indicate a mental confusion about the risks involved. For instance, assert(2 + 2 == 4);
Witch style (F1, F2 ..) better represents the guidelines of your code?
//The function f never returns nullptr^^^^^^
int * f();
void F1() {^^^^
int* p = f();
if (p == nullptr) {^^^
assert(false);
return;
}
// using *p ...
}
void F2()
{
int* p = f();
assert(p != nullptr);^^^^
// using *p ...
}
void F3()
{
int* p = f();
// using *p ...
}
void F4()
{
int* p = f();
I also have a interesting sample with linked list
On 03/09/2024 11:53, David Brown wrote:
On 03/09/2024 16:12, Thiago Adams wrote:
A safeguards for a very low risk situation also may indicate a mental
confusion about the risks involved. For instance, assert(2 + 2 == 4);
A redundant check is, by definition, a very low risk situation.
I will give a sample
but...maybe, is better to be a little redundant here?
I think I prefer to leave "obj->member1->member2 && " even if I know
it should not be null.
if (obj->member1 &&
obj->member1->member2 &&
obj->member1->member2->member3)
{
}
David Brown <david.brown@hesbynett.no> writes:
[...]
Before you put any check in code, think about the circumstances in[...]
which it could fail. If there are no circumstances, it is redundant
and counter-productive.
One thing to consider is that if a check can never actually fail the
recovery code *cannot be tested* (and you can't get 100% code coverage).
p = NULL; // assume p is not volatile
if (p != NULL) {
do_something(); // can never execute this
}
Of course not all such cases are so easily detectible (
The contract is
* obj->member1 CAN be null
* obj->member1->member2 CANNOT be null
* obj->member1->member2->member3 CAN be null
On 04/09/2024 03:45, David Brown wrote:
On 03/09/2024 18:37, Thiago Adams wrote:
I also have a interesting sample with linked list
Speaking generally rather than specifically about this code, your
extra checks are redundant when the code is run from a single thread,
and insufficient if it is later used from multiple threads (accessing
the same data). But extra checks can fool people into thinking it is
safe - since the checks are unnecessary for sequential code they must
have been put there to catch rare race conditions.
There are times when you want seat-belts /and/ airbags. But focusing
on that instead of making sure the driver is competent is not helpful!
In Cake (my front-end), I made `assert` a special built-in function to
allow it to be used by static analysis.
For instance:
`assert(p != 0);`
Static analysis will then assume that `p` is not zero after this line.
However, this raises a question:
Does assert overrides what the compiler already knows?
For example:
int i = 1;
assert(i == 2);
Here, the compiler knows `i` is `1`, but the `assert` (i.e., the
programmer) is claiming that it’s `2`!
I then reconsidered my view. `Assert` is about what the programmer is
stating to be true. (Programmer can be wrong) It is not a "override
command".
I think this is applicable for any "assume concept".
C++ 23 added [[assume( expression )]]
"Tells the compiler to assume that an expression will always evaluate to
true at a given point ..." "The purpose of an assumption is to allow
compiler optimizations based on the information given"
There is a big warning there :
"IMPORTANT: DANGEROUS OPERATION, USE WITH CARE"
https://en.cppreference.com/w/cpp/language/attributes/assume
I also may add this [[assume]] to cake, but assert also gives me a
runtime check.
In the first example `assert(p != 0)`, the compiler knows that `p` is
MAYBE `null`. But the programmer is asserting that `p` cannot be `null`,
so the compiler will follow the programmer's assertion.
Perhaps the programmer knows something the compiler doesn’t?
This is similar to a case with a linked list, where the programmer knew
that if the `head` is `null`, then the `tail` must also be `null`.
However, if the compiler is certain about a fact and encounters an
`assert` with conflicting information, it should trigger a warning.
For example:
int i = 0;
assert(i != 0); // Warning
When the compiler finds an assert, that is not adding extra information
but only saying what the compiler already knows, then we don't have
warnings. But, this is a redundant check.
int i = 0;
assert(i == 0); // ok
On the other hand, redundant 'if' I have a warning.
if (i == 0) {} //warning 'i' is always 0 here!
So, basically.
- if assert has a conflicting information compared with what compiler
already knows, then it is a warning. Compiler will not override what it knows?
- If the assert gives the exactly information compiler already have,
then is is not a warning. Both compiler and programmers knows the same
thing.
- If the compiler is not sure about a fact and the programmer gives a plausible fact then the compiler will assume the programmer is correct.
On 2024-09-03, Thiago Adams <thiago.adams@gmail.com> wrote:
The contract is
* obj->member1 CAN be null
* obj->member1->member2 CANNOT be null
* obj->member1->member2->member3 CAN be null
Newer languages have null-safe object access operators:
if (obj?->member1->member2?->member3) ...
We get the correct check, and, at a glance, the question marks annotate
what the coder believes is the contract: obj may be null, obj->member1->member2 may be null.
GCC could easily get this extension, it seems.
obj?->member is just obj ? obj->member : nullptr except that
obj is evaluated only once.
assert/assume is more
"trust me compiler, this is what happens" someone else is checking this
we don't need to check again.
but...maybe, is better to be a little redundant here?
I think I prefer to leave "obj->member1->member2 && " even if I know
it should not be null.
if (obj->member1 &&
obj->member1->member2 &&
obj->member1->member2->member3)
{
}
On 03/09/2024 17:23, Thiago Adams wrote:
<snip>
but...maybe, is better to be a little redundant here?
I think I prefer to leave "obj->member1->member2 && " even if I know
it should not be null.
if (obj->member1 &&
obj->member1->member2 &&
obj->member1->member2->member3)
{
}
I think I'd prefer to _omit_ the check that obj->member1->member2 isn't
null.
If the code is running correctly that check will never trigger, and is redundant, and will slow things slightly.
Sysop: | Keyop |
---|---|
Location: | Huddersfield, West Yorkshire, UK |
Users: | 429 |
Nodes: | 16 (2 / 14) |
Uptime: | 117:15:07 |
Calls: | 9,056 |
Calls today: | 3 |
Files: | 13,396 |
Messages: | 6,016,551 |