So, what is one to do to avoid proverbially impaling oneself on C pointers?
Not always maybe, i have no clue about bank-switching, just to be safe you better :
... I sincerely hope others will tell what they do, if they do avoid pointer bugs in their C work product, because that is just more tools in the box to choose from.
I develop a lot of safety critical firmware in C and bugs due to pointers are VERY rare. Most of the time it is just a matter of applying coding standards (e. g. MISRA) that define rules for pointer arithmetic AND enforcing them via static code analysis tools and/or reviews, of course. Additionally you can do things like letting your static analysis tool assume that every pointer passed to a function is potentially NULL and complain if there is no execution path for ptr==NULL, for example.
Sticking to reasonable coding rules, plus proper static analysis and testing makes things a lot better.
When you make a decision about something, you can either
- follow rules (or commands)
or
- think
Therefore, if you follow rules, you don't think.
Thinking is necessary. Rules explicitly suppress thinking. This is why bureaucracies stifle progress.
Both are not mutually exclusive. Actually, when they are, that means one of the two has been taken to a dysfunctional extreme.
So, the purpose of these rules, is to restrict the solution domain to a subset of the complete language, which is easier to be correct within. That may be some imposition to the developer, requiring extra boilerplate, or roundabout solutions where a simpler but less reliable or verifiable method might do (e.g. employing dynamic memory or recursive functions, neither of which is required for a lot of problems), but it's not about making whole solutions impossible.
But they always are. Thinking leads you towards making your own decisions. Rules are decisions made for you.If that were exactly true, how would you explain chess?
If that were exactly true, how would you explain chess?
So we add rules to them. Digital logic is a subset of analog, where we can use fewer rules to describe the elements. The resulting elements are far easier to reason about, and much more stable and consistent. We can use simple logic simulations, and guard-banding min/max design rules, to verify circuit operation. Instead of being challenged by mere hundreds of transistors, we can bring billions to bear!
Well, I couldn't disagree more :)Sticking to reasonable coding rules, plus proper static analysis and testing makes things a lot better.So, my advise is - forget the rules, think.
"Goto" is allowed, but only in critical code, where it makes sense.Correct. MISRA for example has an "advisory" rule that forbids the use of goto. But MISRA also acknowledges the fact that code without goto could be less transparent than the goto it replaces, i. e. if the code required additional flags to get rid of goto. And MISRA therefore defines rules for using goto in a safe way, e. g. only jumping "down" and to a point in the same function.
Abuses are prone to produce spaghetti code, terrible for the ICE.
//------------------------------------------------------------------------------
// Linked List Macros ( uses 2 dummys ( first & last ) )
// note : in the object constructor : pList is not valid
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
// LIST_VARIABLES adds variables to list class
//------------------------------------------------------------------------------
#define LIST_VARIABLES( objectname ) \
friend class objectname; \
objectname*first,*last,*current;
//------------------------------------------------------------------------------
// OBJECT_VARIABLES adds variables to object class
//------------------------------------------------------------------------------
#define OBJECT_VARIABLES( objectname , listname ) \
friend listname; \
objectname*next,*prev; \
listname*pList;
//------------------------------------------------------------------------------
// LIST_CONSTRUCT use this in the constructor
//------------------------------------------------------------------------------
#define LIST_CONSTRUCT first = last = current = NULL;
//------------------------------------------------------------------------------
// LIST_DESTRUCT use this in the destructor
//------------------------------------------------------------------------------
#define LIST_DESTRUCT( objectname ) \
current = first; \
\
while( current ) \
{ \
objectname*destroy = current; \
current = current->next; \
delete destroy; \
}
//------------------------------------------------------------------------------
// LIST_INIT creates 2 dummys for fast linking and initialize
//------------------------------------------------------------------------------
#define LIST_INIT( objectname ) \
/* create dummys */\
if( !( first = new objectname ) || !( last = new objectname ) )return false; \
\
/* link dummys */\
first->next = last; \
last->prev = first; \
\
/* terminate for deletion */\
last->next = NULL;
//------------------------------------------------------------------------------
// LIST_REMOVE ( removes all objects exept the 2 dummys )
//------------------------------------------------------------------------------
#define LIST_REMOVE( objectname ) \
current = first->next; \
\
while( current != last ) \
{ \
objectname*destroy = current; \
current = current->next; \
delete destroy; \
} \
\
/* link dummys */\
first->next = last; \
last->prev = first;
//------------------------------------------------------------------------------
// LIST_ADDFRONT adds new object in front of list
//------------------------------------------------------------------------------
#define LIST_ADDFRONT( objectname ) \
objectname*temp = new objectname; \
temp->pList = this; \
\
temp->prev = first; \
temp->next = first->next; \
first->next->prev = temp; \
\
first->next = temp;
//------------------------------------------------------------------------------
// LIST_ADDBACK adds new object in back of list
//------------------------------------------------------------------------------
#define LIST_ADDBACK( objectname ) \
objectname*temp = new objectname; \
temp->pList = this; \
\
temp->next = last; \
temp->prev = last->prev; \
last->prev->next = temp; \
\
last->prev = temp;
//------------------------------------------------------------------------------
// LIST_FUNCTION uses functions from whole list exept the 2 dummys
//------------------------------------------------------------------------------
#define LIST_FUNCTION( func ) current = first->next; while( current != last )current->func();
//------------------------------------------------------------------------------
// OBJECT_DELETE delete object and link prev with next !use as last thing!
//------------------------------------------------------------------------------
#define OBJECT_DELETE \
next->prev = prev; \
prev->next = pList->current = next; \
delete this;
//------------------------------------------------------------------------------
// LIST_EMPTY check if list is empty
//------------------------------------------------------------------------------
#define LIST_EMPTY first->next == last
You seem to disregard the possibility that there could ever be a rule which is beneficial. Can you give examples of rules, used in software design, that you find restrictive or indeed useless? And can you provide evidence and arguments to defend them as such?
... "I can do anything I want, no one can tell me what/not to do!".
Sorry havent looked to my macros since i made them :Code: [Select]
//------------------------------------------------------------------------------
// Linked List Macros ( uses 2 dummys ( first & last ) )
// note : in the object constructor : pList is not valid
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
// LIST_VARIABLES adds variables to list class
//------------------------------------------------------------------------------
#define LIST_VARIABLES( objectname ) \
friend class objectname; \
objectname*first,*last,*current;
//------------------------------------------------------------------------------
// OBJECT_VARIABLES adds variables to object class
//------------------------------------------------------------------------------
#define OBJECT_VARIABLES( objectname , listname ) \
friend listname; \
objectname*next,*prev; \
listname*pList;
//------------------------------------------------------------------------------
// LIST_CONSTRUCT use this in the constructor
//------------------------------------------------------------------------------
#define LIST_CONSTRUCT first = last = current = NULL;
//------------------------------------------------------------------------------
// LIST_DESTRUCT use this in the destructor
//------------------------------------------------------------------------------
#define LIST_DESTRUCT( objectname ) \
current = first; \
\
while( current ) \
{ \
objectname*destroy = current; \
current = current->next; \
delete destroy; \
}
//------------------------------------------------------------------------------
// LIST_INIT creates 2 dummys for fast linking and initialize
//------------------------------------------------------------------------------
#define LIST_INIT( objectname ) \
/* create dummys */\
if( !( first = new objectname ) || !( last = new objectname ) )return false; \
\
/* link dummys */\
first->next = last; \
last->prev = first; \
\
/* terminate for deletion */\
last->next = NULL;
//------------------------------------------------------------------------------
// LIST_REMOVE ( removes all objects exept the 2 dummys )
//------------------------------------------------------------------------------
#define LIST_REMOVE( objectname ) \
current = first->next; \
\
while( current != last ) \
{ \
objectname*destroy = current; \
current = current->next; \
delete destroy; \
} \
\
/* link dummys */\
first->next = last; \
last->prev = first;
//------------------------------------------------------------------------------
// LIST_ADDFRONT adds new object in front of list
//------------------------------------------------------------------------------
#define LIST_ADDFRONT( objectname ) \
objectname*temp = new objectname; \
temp->pList = this; \
\
temp->prev = first; \
temp->next = first->next; \
first->next->prev = temp; \
\
first->next = temp;
//------------------------------------------------------------------------------
// LIST_ADDBACK adds new object in back of list
//------------------------------------------------------------------------------
#define LIST_ADDBACK( objectname ) \
objectname*temp = new objectname; \
temp->pList = this; \
\
temp->next = last; \
temp->prev = last->prev; \
last->prev->next = temp; \
\
last->prev = temp;
//------------------------------------------------------------------------------
// LIST_FUNCTION uses functions from whole list exept the 2 dummys
//------------------------------------------------------------------------------
#define LIST_FUNCTION( func ) current = first->next; while( current != last )current->func();
//------------------------------------------------------------------------------
// OBJECT_DELETE delete object and link prev with next !use as last thing!
//------------------------------------------------------------------------------
#define OBJECT_DELETE \
next->prev = prev; \
prev->next = pList->current = next; \
delete this;
//------------------------------------------------------------------------------
// LIST_EMPTY check if list is empty
//------------------------------------------------------------------------------
#define LIST_EMPTY first->next == last
They just work, for C++.
"Goto" is allowed, but only in critical code, where it makes sense.
Abuses are prone to produce spaghetti code, terrible for the ICE.
For our example (C pointers) the tools for more or less avoiding bugs altogether are out there and well proven. And whether you like it or not these tools are rules and their enforcement with things like static code analysis.
Hmm. Why should I do this in C++ if std::list is available anyway? (or boost::intrusive::list, if I need an intrusive variant of a list)
There are other bugs where the pointer points to a valid memory, but not where it should. Bad access may corrupt something. These are slightly more difficult to fix because the cause and the effect are separated in time.
No dice. Everything is a game; that's why John Nash became famous. See game theory (https://en.wikipedia.org/wiki/Game_theory). In a very real, mathematically provable sense, all interactions between rational beings can be modeled as games (with "win" or "lose" similarly arbitrarily selectable); even the definition of game in game theory is "interactions between rational beings". There is no "play" or "pretend" implied or assumed here.If that were exactly true, how would you explain chess?
Chess is a game. It is abstract and defined by rules.
They asked me whether I use "goto" while coding. And I said yes. They didn't hire me, told me that I didn't know modern (back then) programming theory and gave me an academic paper which explained that using "goto" is pure evil and leads to unmanageable spaghetti code.Yes; this too is why I so often need to put "knowledge" and "know" in quotes.
"goto" as is is a very poor construct.The labels-as-values extension (with unary && operator providing the compile-time constant address of a label, and goto *ptr; to jump to any label thus acquired) as implemented by GCC and LLVM/Clang, covers additional shortcomings of C, and can be actually useful in rare cases.
int my_func(size_t doubles, size_t ints, size_t chars)
{
double *darray = malloc(doubles * sizeof (double));
int *iarray = malloc(ints * sizeof (int));
char *carray = malloc(chars);
if (!darray || !iarray || !carray) {
free(carray);
free(iarray);
free(darray);
return NOT_ENOUGH_MEMORY;
}
// Do stuff ...
free(carray);
free(iarray);
free(darray);
return SUCCESS;
}
Because free(NULL) is safe and does nothing, we can do all the allocations we want, and then just check if any of them failed. If any of them failed (is NULL), we free all of them, and return the not-enough-memory error. Yes, we do call free(NULL) on the failed allocations, but it is minimal overhead, and safe; thus worth it for the simpler, easier to maintain code."goto" as is is a very poor construct.The labels-as-values extension (with unary && operator providing the compile-time constant address of a label, and goto *ptr; to jump to any label thus acquired) as implemented by GCC and LLVM/Clang, covers additional shortcomings of C, and can be actually useful in rare cases.
if (step1) {
undo1();
return failure;
}
if (step2) {
undo2();
undo1();
return failure;
}
if (step3) {
undo3();
undo2();
undo1();
return failure;
}
return success;
seems the nicest and cleanest of them all, but deceptively so: the code duplication in the failure branches (which tend to be the least tested ones) means that any changes must be correctly reflected across all error branches; and this makes it annoyingly easy to fix a bug or add a detail to one, but not all, error branches.
- Jumping to some common code section, for instance for error handling inside a function.
Aren't rules there because we share our code (willingly or not!) with other people, and it is sensible to have a common language.Good point/question. I personally do not know. I keep all that stuff under the Maintainability heading, and they're very important to me; I just don't know if I'd call them rules, sensible advice, or information gained through past experience, or something else.
I fully agree; I intended to list them as "also, these are related but cover a different set of shortcomings, or at least provide an option for some specific rare scenarios, but are similarly not a tool you should often reach for". Bad wording on my part.I have no doubt they can be very useful, but they certainly do not address the shortcomings I was referring too. I see those more as band-aids."goto" as is is a very poor construct.The labels-as-values extension (with unary && operator providing the compile-time constant address of a label, and goto *ptr; to jump to any label thus acquired) as implemented by GCC and LLVM/Clang, covers additional shortcomings of C, and can be actually useful in rare cases.
if (step1) {
undo1();
return failure;
}
if (step2) {
undo2();
undo1();
return failure;
}
if (step3) {
undo3();
undo2();
undo1();
return failure;
}
return success;
Similarly, you cannot learn programming by being afraid of bugs.
When you make a decision about something, you can either
- follow rules (or commands)
or
- think
Therefore, if you follow rules, you don't think.
QuoteTherefore, if you follow rules, you don't think.
This is wrong. I see a rule and the first thing I want to know is why it's a rule. What is it fixing, and often just knowing that there is a rule for something makes me aware of, and learn about, something I hadn't previously considered. What happens about it then would depend on the specific situation, but generally a rule is prior experience pointing a finger.
The final line there might pass as workable bon mot but perhaps it merely reflects one person's narrow view.
I see a rule and the first thing I want to know is why it's a rule.Human after my own heart! Me too.
As to fear of bugs: I don't avoid bugs because they scare me; they do not. They always happen, just like taxes. I only want to avoid bugs because they annoy the heck out of me. So, I trade time and effort for not getting annoyed too often.
If you look at every line as if it already contained a bug (which it most likely doesn't), and try to figure what the bug can possibly be, and try to fix it even before your write that line, you make your job needlessly hard. If it's not broken, don't fix it.
If it's not broken, don't fix it.
No, being told there is a bug is never a positive event.
Fixing it can be, but the existence of it is definitely a negative situation.
QuoteIf it's not broken, don't fix it.
How about when it is broken but it just hasn't triggered a noticeable issue yet? Surely you would want to have it as bug-free as possible rather than waiting for some Bad Thing to occur?
Further, simple finding (or, more likely, being told) a bug is not a good move. There is a fair chance that you are unable to fix the bug without serious rework, and potentially it is not fixable at all.
Don't forget that the earlier a bug is caught the cheaper it is to fix.
But also when someone reports the bug to you, your programming reputation will have taken a hit and will take some effort to recover (if it ever does).
No, being told there is a bug is never a positive event. Fixing it can be, but the existence of it is definitely a negative situation.
Quote from: dunkemhigh on Today at 17:55:56
No, being told there is a bug is never a positive event. Fixing it can be, but the existence of it is definitely a negative situation.
How so? The bug existed before. If it wasn't found, it would persist, possibly causing harm. The only difference is that you wouldn't know about it. Now, when it's found, you can stop the harm you were causing. Would you really prefer to remain delusional?
Customers are also totally used to bugs not being taken seriously and ever fixed. See Altium Designer for example.
Personally, I get a bigger kick out of writing bug-free code than I do having to fix my mistakes afterwards.
I realized I left out a crucial word there: writing.As to fear of bugs: I don't avoid writing bugs because they scare me; they do not. They always happen, just like taxes. I only want to avoid bugs because they annoy the heck out of me. So, I trade time and effort for not getting annoyed too often.Look at that this way. Once you have written software, it will have bugs. Here, there ... You don't know where they are and therefore you cannot fix it. Your software is buggy and crappy. Then, you encounter a bug. Either you find it yourself, or QA engineer filed a report, or user filed a support ticket. This is good luck for you. This gives you an opportunity to work on it and fix it. This will make your software better, and your software will eventually becomes clean and reliable. If you didn't find the bug, the software would remain crappy.
How can you possibly look at this very positive event as if someone is throwing dog shit at you?
If you cannot tell where the bugs are after you write the software, you certainly cannot tell where they are before you write the software. Of 100 lines you write, may be one or two will have bugs, may be even none. If you look at every line as if it already contained a bug (which it most likely doesn't), and try to figure what the bug can possibly be, and try to fix it even before your write that line, you make your job needlessly hard. If it's not broken, don't fix it.Yes, and no. If I have no reason to suspect a code does not behave as it is supposed to, I don't worry about it.
I think the biggest software system defects aren't implementation bugs but requirements / specification / documentation bugs.Yes, I think I have to agree. I do feel they occur in a slightly different context, though: instead of silently garbling data, they just refuse to work in an useful manner, or forbid actions or expressions that the user needs to perform their tasks. (Lots of compiler features in this class! Yet, I still use C and C++.)
These are just simple human mistakes - something misspelled, misjudged, or misunderstood. You cannot prevent such mistake from happening. You can only fix them afterwards.I'm not sure I even think of them as proper bugs anymore; they're so common and easy to fix. I myself like to sleep over an initial implementation, then review it the next day, because those always occur – like taxes! – and I consider it just part of my development workflow, not "bug hunting" per se.
These are just simple human mistakes - something misspelled, misjudged, or misunderstood. You cannot prevent such mistake from happening. You can only fix them afterwards.I'm not sure I even think of them as proper bugs anymore; they're so common and easy to fix.
I realized I left out a crucial word there: writing.
So it is hard to reconcile why you would "get ecstatic" when someone finds you've fucked up, confused them and they've spend some effort generating a report which they wouldn't've had to do if you'd done your job right in the first place.
... someone finds you've fucked up ...
That's why as a customer I want to be able to invest my time writing a good error report and want it to be taken seriously.
These are just simple human mistakes - something misspelled, misjudged, or misunderstood. You cannot prevent such mistake from happening. You can only fix them afterwards.I'm not sure I even think of them as proper bugs anymore; they're so common and easy to fix.
But all bugs are like this. They're your mistakes of various kinds. While you can catch some of them in code reviews, most are best visible when you run your code and observe the effects.
That's why as a customer I want to be able to invest my time writing a good error report and want it to be taken seriously.
Me too. But unfortunately the majority of companies don't accept bug reports. At best you get a support person whose job is to help customers to use the product. She doesn't know how to file a bug report even if she wanted to. This discourages customers and they stop submitting bug reports. This is one of the reasons why software is getting so buggy.
Consider two scenarios:
Only the simple ones; they are so common and natural that I don't consider them "bugs" per se, just part of the expected, normal work flow.These are just simple human mistakes - something misspelled, misjudged, or misunderstood. You cannot prevent such mistake from happening. You can only fix them afterwards.I'm not sure I even think of them as proper bugs anymore; they're so common and easy to fix.
But all bugs are like this. They're your mistakes of various kinds. While you can catch some of them in code reviews, most are best visible when you run your code and observe the effects.
Besides which, I take pride in a job well done. It ain't well done if it's faulty.
Besides which, I take pride in a job well done. It ain't well done if it's faulty.
So, you say you never make any bugs. May I ask how do you know that?
Where did I say I never make mistakes? In fact, throughout this thread I've said I do make them. You're making shit up so you can knock it down, and that's not a good way to carry an argument.
... you encounter a bug. Either you find it yourself, or QA engineer filed a report, or user filed a support ticket. This is good luck for you. This gives you an opportunity to work on it and fix it.
No, being told there is a bug is never a positive event.
Consider, instead, a scenario where you don't get to step 3 at all. Isn't that better?
I take pride in a job well done. It ain't well done if it's faulty.
You argue that discovering a bug is a bad thing. Here:
Quote from: dunkemhigh on Yesterday at 17:55:56
No, being told there is a bug is never a positive event.
When I try to show you why finding a bug is positive and want to hear your side of the story, you answer that it's better not to have any bugs at all
So, am I going to get any answer why is it bad to find a bug in your code?
Acutally, no. Being told there is a bug is a bad thing. Discovering a bug (thus preventing being told about it) is a good thing, but not in the sense that one gets a kick out of it.
So you prefer to have bugs than not have bugs? Do you consciously add them just so you can feel ecstatic from fixing them?
That's an outrageous take on your comment, but it is exactly what you are doing to my comments.
And, yes, it is massively better not to have bugs. That isn't saying it won't have, or that one never makes mistakes (which is apparently what you perversely interpreted it to mean). But I'll humour you: why is bug-free code not better than buggy code? Seriously, I cannot think of a single valid reason.
Do you consciously add them just so you can feel ecstatic from fixing them?If that's re my comment, I definitely do not. Chemically, the dopamine kick comes from improving something, and is more than negated by any conscious (and usually even any unconscious and purely accidental) act of damage.
QuoteBut I'll humour you: why is bug-free code not better than buggy code? Seriously, I cannot think of a single valid reason.
I can. If your software has less bugs, people will buy more.
Do you consciously add them just so you can feel ecstatic from fixing them?If that's re my comment, I definitely do not.
So, what's my point? It is, can smart pointers ala C++ be of any help here.Very true. I like the way you put it, because it highlights that C++ is not the same language as C (exactly because it tries hard to avoid the shortcomings in C and has a very different set of abstractions).
Take a look to where the world is going,
they expect you to take the garbage they give, so they can fix it later over internet or USB.
Else they dont make any money if people buy the other brand, that do has a buggy release.
Do you ever hear about bugs in CASIO ?