Author Topic: Mind over bugs: C pointer edition  (Read 10211 times)

0 Members and 1 Guest are viewing this topic.

Offline Feynman

  • Regular Contributor
  • *
  • Posts: 192
  • Country: ch
Re: Mind over bugs: C pointer edition
« Reply #25 on: June 30, 2021, 11:12:06 am »
"Goto" is allowed, but only in critical code, where it makes sense.
Abuses are prone to produce spaghetti code, terrible for the ICE.
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.

The harmfulness of goto is pretty much known since 1968 and every serious set of rules should generally discourage a programmer from using it.
 

Offline Jan Audio

  • Frequent Contributor
  • **
  • Posts: 820
  • Country: nl
Re: Mind over bugs: C pointer edition
« Reply #26 on: June 30, 2021, 02:12:39 pm »
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++.
 

Offline NorthGuy

  • Super Contributor
  • ***
  • Posts: 3146
  • Country: ca
Re: Mind over bugs: C pointer edition
« Reply #27 on: June 30, 2021, 02:38:08 pm »
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'll give you one example, which hit me personally.

When I was young I wanted to take a job as a programmer and I went to a job interview. 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. If it was now, it would probably also said that if I use "goto" I also kill all the chances for the compiler to optimize the code, but this wasn't the case back then.

So, I made a resolve to improve my coding and decided to learn how to program without goto. I made a rule for myself never to use "goto". It was difficult at first, but after several months I got much better at this and I was very proud of my code which didn't have any "goto"s. It took me few years to realize that it doesn't bloody matter. I wasted countless hours doing this, and all I gained is a bad habit (which is difficult to get rid off, even now).

Lost time alone wouldn't be that bad. But the thinking about "goto"s shifted my attention from important things. Instead of thinking about what I program, finding better data structures and algorithms, I was wasting my efforts on eliminating "goto". I'm not proud of what I did. Now I understand that I would be better off using my own brain.

... "I can do anything I want, no one can tell me what/not to do!".

May be you worded this a little bit too extreme, but that's what I advocate. Programmers are perfectly capable of deciding how to program.
 
The following users thanked this post: Siwastaja, Nominal Animal

Online gf

  • Super Contributor
  • ***
  • Posts: 1179
  • Country: de
Re: Mind over bugs: C pointer edition
« Reply #28 on: June 30, 2021, 03:13:15 pm »
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++.

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)
 

Offline Siwastaja

  • Super Contributor
  • ***
  • Posts: 8173
  • Country: fi
Re: Mind over bugs: C pointer edition
« Reply #29 on: June 30, 2021, 03:24:47 pm »
"Goto" is allowed, but only in critical code, where it makes sense.
Abuses are prone to produce spaghetti code, terrible for the ICE.

But isn't this obviously true for practically any construct in the language?
« Last Edit: June 30, 2021, 03:29:55 pm by Siwastaja »
 

Offline NorthGuy

  • Super Contributor
  • ***
  • Posts: 3146
  • Country: ca
Re: Mind over bugs: C pointer edition
« Reply #30 on: June 30, 2021, 03:26:21 pm »
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.

This is very simple actually. If you access a variable through a pointer which points outside of valid memory, the program will crash. That's all there is to it. I don't think any amount of rules or any amount of static analysis can catch all such cases. You just analyze the crash, find the cause, and fix it.

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.
 

Offline Jan Audio

  • Frequent Contributor
  • **
  • Posts: 820
  • Country: nl
Re: Mind over bugs: C pointer edition
« Reply #31 on: June 30, 2021, 03:28:04 pm »
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)

Because it is the same or better, you can adjust, my file is much bigger then these basics i just posted.
I dont use other persons programming.

I dont know what is intrusive, i dont want to add any include files and especially no .dll files.
I want source-code only, if they give the code, then i still adjust it to my own.
Mostly they dont give anything.
« Last Edit: June 30, 2021, 03:32:15 pm by Jan Audio »
 

Offline Siwastaja

  • Super Contributor
  • ***
  • Posts: 8173
  • Country: fi
Re: Mind over bugs: C pointer edition
« Reply #32 on: June 30, 2021, 03:30:14 pm »
NorthGuy, thanks for the goto story. It kinda confirms and clears the idea in my head; the problem with such arbitrary rules is that they are not just technical misunderstandings by a rule writer, no, they are identity politics. You are supposed to show "professionalism" by following the trends, just like now you are not supposed to call master devices master devices anymore, and can fail getting a job if they ask you a trick question about the default git branch name and you answer "master". This is pure evil shit.

The question regarding rule sets really is, to which extent it's identity politics, and to which extent sane rules with technical purposes. And that depends a lot, there are good rules for good reasons, too.
 

Offline Jan Audio

  • Frequent Contributor
  • **
  • Posts: 820
  • Country: nl
Re: Mind over bugs: C pointer edition
« Reply #33 on: June 30, 2021, 03:54:30 pm »
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.

Then bugs show up everywhere, where there is no bug, tell me about it.
The fun part is that all systems show different bugs, you will be able to trace it sooner if you testing on 3 different systems ( PCs ).
 

Offline Nominal AnimalTopic starter

  • Super Contributor
  • ***
  • Posts: 6264
  • Country: fi
    • My home page and email address
Re: Mind over bugs: C pointer edition
« Reply #34 on: June 30, 2021, 04:23:44 pm »
If that were exactly true, how would you explain chess?

Chess is a game. It is abstract and defined by rules.
No dice.  Everything is a game; that's why John Nash became famous.  See 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.

Choice is the key.  See combinatorial explosion.

Simply put, if you have Conway's Game of Life with four rules, an m by n world, the information it contains is just 2mn (plus the information needed to store the rules, which I believe is either 256 or 512 bits for any possible rule set involving only the nearest neighbor cells), no matter how long the world would evolve.

Consider a simple game on a m by n board, where each player gets to move their piece one step in any of the four cardinal directions.  Ignoring the minute difference whether we allow or deny them occupying the same position, the board has 22mn possible states.  However, because of having the ability to choose, the sequence of position over K turns is much larger.  Even if you add a hundred rules, the size of the phase space of all possible sequences – the amount of information required to describe such a game – is infinitely larger.  If there is no stop conditon, it is infinite.  The only reason chess has a limited phase space, is because it has rules/stop conditions regarding cyclicity (impasse, I think? I'm too stupid to be much good at chess myself).



Consider what I wrote above about C pointers.  They basically have no rules the C compiler enforces.  If you want a pointer of qualified type TYPE to point to address ADDRESS given as either a pointer or a numerical address (compatible to intptr_t or uintptr_t type), then you do (TYPE *)(ADDRESS).

I have claimed based on limited personal observation that the majority of bugs related to C pointers stem from not seeing anything wrong or suspect in such expressions.

So, we derive rules, based on observations of such expressions.  Mine can perhaps be summarised as
  • Make sure the target address is valid, that it points to what I think it does.
  • Make sure the target address is sufficiently aligned for the hardware requirements.
  • Make sure the entire address range implied by the pointer is valid.  A four (say, uint32_t) or eight-byte (say, uint64_t) access even at a sufficiently aligned address in a byte buffer may not be valid, if the content in the buffer does not cover the entire accessed element.
  • Record your assumptions, and if reasoning was required, the basis of that reasoning in a comment.  It serves as an orthogonal support for the maintenance of the code (describing important features not described by the code itself), as well as a tool for organizing ones thoughts (rubber duck): it saves total effort.
  • Record the basis of expressions that are the result of effective simplification from an originally complicated form.  The originally complicated form may express assumptions and logic that are not expressed in the effective simplified form.

    As a mathematical example, consider the sum of n consecutive integers starting at k.  This simplifies to k n + (n2 - n)/2.  This simplified form does not imply the sum; therefore if the sum is the mathematical/logical thing needed to express or evaluate, just having the simplified form loses significant amount of information needed for a developer to see whether the code implements the logic the author intended it to or not.  Including the original reason (sum of n consecutive integers starting at k) as a comment, lets further developers verify the implementation, and possibly even replace this local optimization with a higher-level algorithmic one (one that eliminates the need for any calculation of this sort, typically).

I understand if your beef is with the definition of what is a "rule", "law", "rule of thumb", et cetera; but fact is, reality trumps theory and words every single time.

Words are only tools, not primary things – think of Magritte's The Treachery of Images (Ceci n'est pas une pipe) – and it is at the very core of what I am suggesting here; and the "rules" we are talking about here are not the kind of rules that restrict choice, these are the sort of rules that help people estimate the effects of any given choice.

Hell, I even tried to allude to this directly in my long example code snippet, in the fact that even it was in a long-ass paragraph describing why one would deliberately set a pointer NULL after freeing or handing it off to another entity taking responsibility for it, I deliberately omitted it and even added a comment why, near the very end!

I'm familiar with this, because every single rational physicist who sees "laws of physics" knows they're not, in any sense of the "law"; except in that one rarely used or understood sense which means "if you choose to behave this way – or apply this model to phenomena you see – you should succeed and prosper".  Or whatever the correct idiom/saying in English is.  Me no English good today.
 

Offline Nominal AnimalTopic starter

  • Super Contributor
  • ***
  • Posts: 6264
  • Country: fi
    • My home page and email address
Re: Mind over bugs: C pointer edition
« Reply #35 on: June 30, 2021, 05:06:36 pm »
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.

To one, rock solid "knowledge", is just an understandable but provably incorrect belief to another.  Having it recorded in a respected scientific journal means much less than what one would think, because up to a quarter of peer-reviewed articles in most respected journals –– much, MUCH more in general –– end up being retracted or amended.

I am not advocating rules of thumb, I am only advocating models one can apply when estimating the results of choices; and the reason this thread exists, is that I hope others would both build on if possible, and tear down those models if necessary, based on their experience and observations.

In short: you, and for example I myself, are not in any disagreement here, just focusing on different aspects of the matter.  Me, on the tools and models available, and roughing them out; you, more on the philosophical aspects and consequences of regarding models as rules, and on codifying specific models as fixed rules with regard to programming.
 

Offline SiliconWizard

  • Super Contributor
  • ***
  • Posts: 14476
  • Country: fr
Re: Mind over bugs: C pointer edition
« Reply #36 on: June 30, 2021, 06:32:41 pm »
The "goto" thing mostly shows that you must understand why you do things and why you'd better not do some others. If you don't know why you stick to some rule, that's a recipe for a lot of frustration, at best, and even counterproductive results, at worst.

"goto" as is is a very poor construct. It was fought against to promote a clean structured programming approach with proper language constructs. This is still valid today, although obviously programming languages have come a long way.

These days, using "goto" in C as a way to handle errors/get out of nested loops in a cleaner way than having to use convoluted structured constructs with a bunch of flags is of course sometimes justified. But it does come from the fact C lacks basic constructs that would make this easy and clean, so you have to resort to "goto".

Although there isn't a  lot of difference (apart from syntax sugar) if you use "goto" sparingly and correctly in C, having, for instance, tagged blocks, and keyword(s) to get out of a tagged block would be much cleaner, more expressive and possibly less bug-prone. If, to this, you add the possibility of declaring "defered" actions, then it gets even better. Of course, some languages implement this kind of features. Apart from looking cleaner, one benefit of tagged blocks is that it gives static analysis the ability to analyze flow control much more precisely.

I'm not at all bashing C here, I'm still a proponent of it, but I know what its shortcomings are, and in particular here, using the "the rule of not using goto is stupid" argument when the only really relevant use of "goto" comes from shortcomings of the language itself has to be considered for what it is.
« Last Edit: June 30, 2021, 06:34:49 pm by SiliconWizard »
 

Offline Nominal AnimalTopic starter

  • Super Contributor
  • ***
  • Posts: 6264
  • Country: fi
    • My home page and email address
Re: Mind over bugs: C pointer edition
« Reply #37 on: June 30, 2021, 09:15:07 pm »
"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.

Specifically, when you implement multiple separate finite state machines in the same scope, as if they were coroutines.  Then, the label to jump to to execute the next state in a particular state machine is kept in a variable.

In most cases, you can just keep the local data in a separate structure, either in a global variable, or by passing the structure to each function handling a state for a particular state machine, so one could argue that it is not strictly needed feature to implement these state machines; but after playing with it, and looking at such code (from a maintainability perspective), I like having the option.



For the newbies who aren't sure what this goto stuff is about:

In my own example snippets I've suggested to others, for the single common error cleanup case I prefer

        do {
            if (step1fails)
                break;
            if (step2fails)
                break;
            if (step3fails)
                break;
            return success;
        } while (0);
        cleanup();
        return failure;

but for staged/structured error cases, goto seems easier to maintain:

        if (step1fails)
            goto fail1;
        if (step2fails)
            goto fail2;
        if (step3fails)
            goto fail3;
        return success;
    fail3:
        undo3();
    fail2:
        undo2();
    fail1:
        undo1();
        return failure;

but I am not opposed to say

        if (step1succeeds) {
            if (step2succeeds) {
                if (step3succeeds) {
                    return success;
                }
                undo3();
            }
            undo2();
        }
        undo1();
        return failure;

either; I kinda like it, but only when there are no silly limitations on line lengths (I often start breaking long lines only after 160 chars or so), as the indentation pushes stuff quite far right, and it is no good if it means expressions then need to be split to multiple lines because of style guides.  However, the following,

        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.  And if the undo steps have any sort of complexity at all, when finally somebody notices they are no longer in sync, they have to go delve in source code management history to see what was modified last and why, just to determine which of the error branches currently have the bug-free version, and should be duplicated to the other branches.  Nasty, when you immediately see a bug (based on differences in the error branches), but there are more than one difference in the error branches: there is no correct one, so one has to grab a cup of favourite beverage, and start analysing the code to see what ones cow-orkers actually tried to accomplish when writing this code – and end up rewriting the entire mess just so oneself does not have to fix it again and again in the future too.  Ask me how I know.

If there is an existing codebase, I use its conventions.  If I have to choose, I pick the one that seems most easy to maintain for that particular case.
If this makes someone reject me as a developer, I don't think I'd like working with them anyway.

And if an employer gives me any lip about fixing bugs affecting production and making the codebase easier to maintain instead of concentrating on new features, I'm outta there.  Fine work for others; I'm not suited for that job.  I, too, am a tool.
 

Offline Nominal AnimalTopic starter

  • Super Contributor
  • ***
  • Posts: 6264
  • Country: fi
    • My home page and email address
Re: Mind over bugs: C pointer edition
« Reply #38 on: June 30, 2021, 09:34:25 pm »
The following pattern is useful when you need to allocate multiple different chunks of dynamic memory in a function:
Code: [Select]
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.

So, although logically we have three steps and possibly seven different possible error cases, we don't need to tie ourselves into the complexity.  Here, we'd win nothing by handling each allocation failure case separately, we'd just make the code more complex.  By handling any allocation failures this way, we keep things simple and robust.

In case you wonder, there is no sizeof (char) in there, because in C sizeof (char) == 1 .  And the proper unsigned integer type for in-memory sizes of types and structures and arrays is size_t.  Depending on the architecture, it is the same type as unsigned int, unsigned long, or unsigned long long, but it is better to think of it as a separate type.  Its conversion specification for print and scan family of functions is %zu (in C99 and later; don't use a C compiler that does not support it).

Also, there is no reason why the free operations should be in reverse order of the allocations; it does not matter for correctness.
I do it, because I like the mirror symmetry; it helps me perceive the code somehow.  If you prefer the same order of frees as allocations, do feel confident to do so; just keep observing yourself to see what helps you write better code, and then do that.  Sometimes a bit of discomfort/unfamiliarity helps one see, sometimes not; find out for yourself.

Someone might point out that doing the frees in the opposite order of the allocations might help the underlying implementation locate the memory chunk faster somehow, but honestly, standard library memory allocation functions are fast, and have to be darned fast to not be annoyingly slow, and use data structures and algorithms that don't really care either way.  It just won't affect their execution speed enough to matter even to speed freaks.  If one is mixing frees and allocations, that's different: you want to do the frees as early as possible, all before any of the allocations if possible, to make room for the future allocations.

Finally, the above function interface has a set of implied assumptions: it assumes none of the three parameters are zero.

You see, malloc(0) may return either NULL, or a non-NULL pointer that cannot be dereferenced but can/must be freed.  (Testing it will only tell you what a particular version of a particular compiler on a particular hardware does; the results are not reliable, because malloc(0) is explicitly allowed to return either one.)
So, as it is currently written, the function may or may not report an allocation error if one or more of the sizes is zero!  Ouch!
There are many ways to handle the zero case as well: you can make the allocation check (and optionally the allocation, or leave it as is) conditional on the size being nonzero [changing the if clause to if ((!darray && doubles > 0) || (!iarray && ints > 0) || (!carray && chars > 0)) {], or you can allocate an extra element for each, and so on.  I did not want to tie anyone to a particular approach, so I left it out.
« Last Edit: June 30, 2021, 09:45:17 pm by Nominal Animal »
 

Offline PlainName

  • Super Contributor
  • ***
  • Posts: 6845
  • Country: va
Re: Mind over bugs: C pointer edition
« Reply #39 on: June 30, 2021, 09:55:53 pm »
Aren't rules there because we share our code (willingly or not!) with other people, and it is sensible to have a common language. Not everyone is a programming god able to instantly see how some weirdo construct implements some ultra-clever Heath Robinson contraption. You may be able to read and understand the spaghetti code you wrote, but someone else might struggle. Thus the reason, for instance, goto is frowned upon is not because you'll tie yourself in knots with it, but someone else will struggle to follow the convoluted path jumped around inside a 4-page function.
 

Offline SiliconWizard

  • Super Contributor
  • ***
  • Posts: 14476
  • Country: fr
Re: Mind over bugs: C pointer edition
« Reply #40 on: June 30, 2021, 10:09:30 pm »
"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.

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.

I for one would never touch those with a stick. First because they are non-standard, and I refrain from using any non-standard language feature, except when they are strictly unavoidable. Second, because frankly, as nice again as 'goto *ptr' can be, it's pretty horrendous to me on several levels. Some of the reasons I mentioned already. Makes static analysis pretty much impossible, and potentially leads to security issues (but for this point I would need to know how exactly it's compiled, so I'm not sure). Makes code hard to read as well. Just my humble opinion there though of course.

My personal take on goto in C is restricting its use to:
- Breaking out of nested loops (at least when any other means would be clunkier);
- Jumping to some common code section, for instance for error handling inside a function.
« Last Edit: June 30, 2021, 10:11:38 pm by SiliconWizard »
 

Online T3sl4co1l

  • Super Contributor
  • ***
  • Posts: 21686
  • Country: us
  • Expert, Analog Electronics, PCB Layout, EMC
    • Seven Transistor Labs
Re: Mind over bugs: C pointer edition
« Reply #41 on: June 30, 2021, 10:14:29 pm »
        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.

It would be nice to have nested functions, so that the undo1() etc. can be de-duplicated while sharing scope; or lambdas or something.  But that's not C.  (C++ however...)

Or you can always pass a laundry list of variables.  Such a PITA though, and more stuff to go wrong (name/order confusion?).

It would be amusing to probe the prior function's stack frame and extract these data.  Too bad that's only going to work in debug, as the compiler otherwise knows not to save variables to memory unless there is a specific need (taking its address, register pressure..).  Or I mean, with rewriting the host function to force its variables into memory.  Very much an advanced trying-too-hard-to-be-clever / desperation / worse abuse than goto trick... :-DD

Tim
Seven Transistor Labs, LLC
Electronic design, from concept to prototype.
Bringing a project to life?  Send me a message!
 
The following users thanked this post: Nominal Animal

Offline DiTBho

  • Super Contributor
  • ***
  • Posts: 3915
  • Country: gb
Re: Mind over bugs: C pointer edition
« Reply #42 on: June 30, 2021, 10:25:04 pm »
- Jumping to some common code section, for instance for error handling inside a function.

I meant this, and only this :D
The opposite of courage is not cowardice, it is conformity. Even a dead fish can go with the flow
 

Offline Nominal AnimalTopic starter

  • Super Contributor
  • ***
  • Posts: 6264
  • Country: fi
    • My home page and email address
Re: Mind over bugs: C pointer edition
« Reply #43 on: June 30, 2021, 10:59:08 pm »
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 do know that worrying about maintainability is very hard when you are starting out as a programmer; it is hard enough to get stuff to work, and trying to make it "nice" or "maintainable" on top is a lot of hard work without immediate rewards.

So, if a beginner were to ask for my advice or opinion, I'd tell them to focus first on writing good comments – orthogonal to the code, meaning try hard to not just say what the code does; try to describe why instead, for a start.  Then, when you start collaborating with friends, talk shop about what sort of code is easiest and most fun to work with (and I mean ignoring what the code does), and what one can do to make it more worthwhile for others to look at, contribute, and work with ones code.  You'll find your own voice and style, but only if you don't get fixated on a specific one "as the Correct or Best one", and try different ones.  When you start regularly getting your programs working the way you want, start working them over with the idea of keeping them around for longer; with all details like build prerequisites and quirks and so on included in the source folder, perhaps a proper LICENSE file and SPDX license identifiers in source files; comments intended to let someone else take over the maintenance.  Heck, you could do some fun projects, maybe terminal-mode network games using curses/ncurses with some friends, and decide that on a certain date, you'd switch projects, just to learn how to take over, maintain, and collaborate with others.

Me, I always talk to the old hands, no matter what the profession.  I'm eager to hear what they've learned, how they've become good at what they do, and what they have to say about doing the job efficiently and satisfactorily and have fun doing it.  And what they want to avoid, and why.  Not all of it will apply to myself, but it might; there is hard-fought experience and information in there, so why not look for it a bit.

I've mentioned before, but one of my own issues, even after three decades of programming as a profession, using half a dozen to a dozen different programming languages, is that I didn't develop my commenting skills early on, and still have some bad habits left.  I have to spend more effort than I would if I had learned to do it correctly early on.  And commenting is not "optional", it is a crucial part of the development process for me, so that does hurt the quality of my work product.  I just didn't really learn this is so until relatively late.

"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.
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.
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.
 

Offline SiliconWizard

  • Super Contributor
  • ***
  • Posts: 14476
  • Country: fr
Re: Mind over bugs: C pointer edition
« Reply #44 on: June 30, 2021, 11:12:34 pm »
        if (step1) {
            undo1();
            return failure;
        }
        if (step2) {
            undo2();
            undo1();
            return failure;
        }
        if (step3) {
            undo3();
            undo2();
            undo1();
            return failure;
        }
        return success;

I have used this pattern quite a bit. It gets clunky pretty fast, but it works. It's somewhat annoying to maintain too.

One way of making this less clunky is to do exactly as you suggested with malloc/free.

Implement things so that your undo*() functions can be called even when the corresponding step hasn't been executed yet. Just requires proper initializations and proper checks.
 

Offline PlainName

  • Super Contributor
  • ***
  • Posts: 6845
  • Country: va
Re: Mind over bugs: C pointer edition
« Reply #45 on: June 30, 2021, 11:34:45 pm »
and...

Quote
Similarly, you cannot learn programming by being afraid of bugs.

I agree with this, if only because learning from a mistake carries far more weight than learning by rote.

However,

Quote
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.

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.
 

Offline NorthGuy

  • Super Contributor
  • ***
  • Posts: 3146
  • Country: ca
Re: Mind over bugs: C pointer edition
« Reply #46 on: July 01, 2021, 01:17:19 am »
Quote
Therefore, 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.

Perhaps I didn't word it well enough. If your employer forces a rule on you, this certainly doesn't preclude you from thinking about the subject and evaluating the merits of the rule. However, what you think doesn't count - you must obey the rule no matter what you think.
« Last Edit: July 01, 2021, 01:19:01 am by NorthGuy »
 

Offline Nominal AnimalTopic starter

  • Super Contributor
  • ***
  • Posts: 6264
  • Country: fi
    • My home page and email address
Re: Mind over bugs: C pointer edition
« Reply #47 on: July 01, 2021, 01:32:37 am »
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.

I sometimes refer to it fondly as my monkey aspect, because it is sometimes seen as the tendency to rattle ones cage and fling dung around, as monkeys are wont to do.
In truth, experiments show that monkeys tend to abide by any rule that is associated with negative feedback; they only go monkeying around when frustrated or hurt.



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.

An analog: dog turds.  I don't fear them.  I don't mind picking up after my own, or a friends' dog; it's just a part of being a dog's (temporary) person, gotta do it.  I've gotten hit by it in the face when walking past a lawnmower, and while annoying and icky, it does not scare me.  Getting hit in the eye with a piece of glass or a rock flung by a lawnmower blade, now that is scary.  Poop is not, it's just annoying, and wastes my time as I have to go and wash.  Or at least hose off my shoes.  Although if it is just on the bottom, I just do that wipe-on-grass thing.

Same with bugs.

I do "fix" some bugs by recording the cause (unacceptable parameter conditions) in a comment in a suitable place, so callers know to avoid the bug, without a single line of functional code.  I do still count that as "fixing".  A common one is something like documenting a Xorshift64* Pseudo-Random Number Generator (my favourites!) with a note that the initial seed has to be nonzero, and if it is, the state will never be zero either; so zero state is always invalid.  I don't bother checking for it, although I do make sure random-seeder (the one that initializes one with a new, random, seed state) never generates one, if intended for a Xorshift PRNG.

Now, having my data silently garbled, that gets me angry.  My data is valuable to me, and "tools" that garble my data, get fixed and/or swapped for better ones.

Right now, I'm seriously considering switching from gcc to clang, exactly because I'm finding myself too often annoyed with what GCC gives me back, and I've started to systematically compare the output of the two.  And for ARM, GCC bugzilla looks like seeing a lawnmower come towards you, with a patch of something glinting in the grass between you and the lawnmower.  I don't even fear getting bit by GCC bugs, because that too occurs and I just deal with it, but turning my back and seeing if the next patch of grass is maybe a bit less glint-y and more suitable for plopping down for a picnic lunch, seems like a prudent option to consider here.

(To be clear, thus far I've used older versions of GCC to keep to versions more stable and less in flux, but now it looks like feature development is greatly exceeding the rate at which bugs are fixed, and bugs are no longer fixed because they occur even in the older versions that the developers have no interest in anymore; and newer versions are too buggy to rely on for any kind of serious work.  I don't want to participate in the bughunt, because I don't care much about the new features they're adding; I'd just be helping people I think are wasting my time, waste even more of my time.)
« Last Edit: July 01, 2021, 01:34:34 am by Nominal Animal »
 

Offline Jan Audio

  • Frequent Contributor
  • **
  • Posts: 820
  • Country: nl
Re: Mind over bugs: C pointer edition
« Reply #48 on: July 01, 2021, 01:06:57 pm »
I am almost considering ASM if i read this.
 

Offline NorthGuy

  • Super Contributor
  • ***
  • Posts: 3146
  • Country: ca
Re: Mind over bugs: C pointer edition
« Reply #49 on: July 01, 2021, 04:20:34 pm »
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.

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.

« Last Edit: July 01, 2021, 04:22:42 pm by NorthGuy »
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf