1

I'm required to write the Low-Level Requirements of a Software Component which shall perform signal processing over arrays of 200k elements of integers/floats which lives in the main memory of the program. The program is single thread.

The software shall be certified in the context of DO 178-C (DAL-A).

The signal processing unit will expose functions like these that will be re-used as needed by the rest of the software:

- low_pass_signal_filter
- element_by_element_vector_multiplcation
- ...

We also are based on MISRA-C 2012 to develop in-house Software Code Standard which actually, allows the use of pointers, but pointers to pointers are disallowed.

Rationale about the use of pointers

In the book "Developing Safety Critical Software", by Leanna Rierson she says:

Page 172:

"Recommendation 9: Ensure the software is deterministic. Safety-critical software must be deterministic; therefore, coding practices that could lead to non-determinism must be avoided or carefully controlled (e.g., self-modifying code, dynamic memory allocation/deallocation, dynamic binding, extensive use of pointers, multiple inheritance, or polymorphism). Well-defined languages, proven compilers, limited optimization, and limited complexity also help with determinism."

And also, in page 172:

"1. Minimize the use of pointers. Pointers are one of the most error-prone areas of programming. I cannot even begin to describe the hours I have spent tracking down issues with pointers—always"

Also, in the book "Embedded Software Development for Safety-Critical Systems" by Chris Hobbs, says at page 255:

"At one end of the spectrum, C’s extensive use of pointers, particularly function pointers, makes static analysis very difficult"

Proposal Solution

Furthermore, I've explored so many ideas, and now I'm just exploring the possibility to avoid passing pointers arguments at all to that library, and then just set and fetch data from a dedicated global array.

Here is a simplified example where I have taken the license to condense two files into one single translation unit for simplicity.

Do you think I should continue exploring this way, or rather stick to pointer parameters as usual?

#include <stdio.h>

/* library.c */

#define MAX_SIZE 10

static int g_input  [ MAX_SIZE ] = { 0 };
static int g_output [ MAX_SIZE ] = { 0 };

extern void process_input ( const int n )
{
    /* process g_input and writes into g_output */
    for ( int i = 0; i < ( n < MAX_SIZE ? n : MAX_SIZE ); i = i + 1 )
    {
      g_output [ i ] = g_input [ i ] * g_input [ i ];
    }

}


/* main.c */
#define SRC_SIZE 4

int main ( void )
{
    int src [ SRC_SIZE ] = { 0, 1, 2, 3 };

    for ( int i = 0; i < (SRC_SIZE < MAX_SIZE ? SRC_SIZE : MAX_SIZE); i = i + 1 )
    {
        g_input [ i ] = src [ i ];
    }

    process_input( SRC_SIZE );

    int dst [ SRC_SIZE ];

    for ( int i = 0; i < (SRC_SIZE < MAX_SIZE ? SRC_SIZE : MAX_SIZE); i = i + 1 )
    {
        dst [ i ] = g_output [ i ];
    }

    for ( int i = 0; i < (SRC_SIZE < MAX_SIZE ? SRC_SIZE : MAX_SIZE); i = i + 1 )
    {
      printf( "%d\n", dst [ i ] );
    }

    return 0;
}
7
  • Why would a global variable be better than a parameter? I'm not familiar with these certifications, can you eloborate on the rationale of "I have to use as fewer pointers as possible." ?
    – Alexander
    Commented Jul 8, 2023 at 21:02
  • 4
    Use of global variables has well-known downsides (not thread safe, not re-entrant, not reusable, harder to test, error prone in libraries that combine different applications/usages, etc..), so if you're ok with them, then so am I ok for you to do that in within your environment & with your requirements. Still, I have difficulty imagining some professional coding standard that advocates this style.
    – Erik Eidt
    Commented Jul 8, 2023 at 21:18
  • How are g_input and g_output used by the rest of the application? Commented Jul 9, 2023 at 0:03
  • How large are the arrays, and how much memory do you have to work with? Commented Jul 9, 2023 at 0:04
  • 2
    I'm not qualified to answer this, but I would guess that just passing addresses to avoid copying of large arguments doesn't qualify as "extensive use of pointers". Furthermore you are allowed to use pointers by your standards. Most problems arise when pointers point to dynamic memory. If you add pointer arithmetic, you're asking for trouble. But here it would just be used as the concept of reference. Although the arrays would live on the heap right? Maybe you could wrap the accesses and writes in functions, OO style.
    – foxesque
    Commented Jul 9, 2023 at 15:40

4 Answers 4

9

We also are based on MISRA-C 2012 to develop in-house Software Code Standard which actually, allows the use of pointers, but pointers to pointers are disallowed.

This means that it is fully allowed to use variables or arguments of the type int*, but that you should not use the type int** (or with even higher levels of indirection).

The reason for this restriction is that with higher levels of indirection, it becomes increasingly harder for a human to reason about the correctness of the code where those pointers-to-pointers are being used. For a single level of indirection, it is still well doable for an average programmer to reason about the code and understand if it contains flaws or not.

Incidentally, there is also a (negative) relation between the size of the (total) codebase and how easy it is to reason about the correctness in the presence of global variables. When global variables are used, especially when they can be accessed from multiple functions or even multiple modules, it becomes increasingly harder to make sure that those functions do not tread on each other's toes.

"Recommendation 9: Ensure the software is deterministic. Safety-critical software must be deterministic; therefore, coding practices that could lead to non-determinism must be avoided or carefully controlled (e.g., self-modifying code, dynamic memory allocation/deallocation, dynamic binding, extensive use of pointers, multiple inheritance, or polymorphism). Well-defined languages, proven compilers, limited optimization, and limited complexity also help with determinism."

And also, in page 172:

"1. Minimize the use of pointers. Pointers are one of the most error-prone areas of programming. I cannot even begin to describe the hours I have spent tracking down issues with pointers—always"

The most important part of this recommendation is the first part, which bears repeating: Ensure the software is deterministic. Everything after it should be weighed against other good programming practices, like avoiding modifiable global data, and the idioms and limitations of the programming language you use.

In C, putting a total ban on the use of pointers is likely to be counter-productive for the aim of creating deterministic software. You can restrict the use of pointers, but they are still the best or only solution for supporting out-parameters and for passing around largish data structures, including arrays.

4
  • It really would be so much simpler if everything were either strictly good or strictly bad in itself, needing none of context, compromise, balance, or weighing against available alternatives. Commented Jul 10, 2023 at 8:11
  • Do you think that implementing a garbage collector might be a reasonable approach to avoid some of the problems associated with the use of pointers? Commented Jul 10, 2023 at 9:06
  • 1
    @Mehdi, that's unlikely given that the recommendation says to minimise dynamic memory allocation/deallocation. And it wouldn't do anything to help pointer arithmetic exceeding bounds. Commented Jul 10, 2023 at 9:44
  • 3
    @MehdiCharife, a garbage collector is unlikely to be acceptable under the constraints of MISRA-C. Garbage collection does not help in making the software deterministic and MISRA-C effectively bans all usecases for a garbage collector. Commented Jul 10, 2023 at 9:53
4

Most people consider globals bad when used in libraries, because that imposes restrictions on how you can use the library in various programs. Libraries usually are more parameterized, using dynamic arguments rather than globals and constants. It makes them more flexible.

Consider if your program needs to grow to handle multiple client connections, for example. Threads, etc. Do you want to re-write your library(-ies) in order to do this?

It's easier to use a more carefully crafted library in a simple single-task program than the converse. The reason you even call it a library implies some amount of portability or re-use is being considered, and thus it's worthy of a bit more care in the crafting. Otherwise it's just another chunk of your program.

I have personally had to refactor many would-be libraries into code that had less globals and #DEFINE constants in them, in order to suit the new target environment.

4
  • Thanks, I get your point. I made an expression mistake, because it is not a library, it is a component in a dedicated software. Already corrected.
    – Sam
    Commented Jul 8, 2023 at 20:13
  • In a mainline program you would have to use dynamic memory, and thus pointers, if your array size needed to vary at runtime. (Unless you could come up with a never-to-be-exceeded maximum size.) Otherwise you can use static-sized arrays. Given C's duality of arrays and pointers, you can use one pointer operation, the allocation, but array syntax for all other uses. Your constraint did recommend against "extensive use of pointers", and I wouldn't consider a single malloc extensive by any means.
    – jimc
    Commented Jul 9, 2023 at 21:20
  • @jimc, safety-related coding standards, like MISRA-C, severely limit the use of dynamic allocation. malloc may only be called at startup in a well-defined sequence and free may not be used at all. In safety-critical software, you do not want to encounter an allocation failure if <circumstances> cause a different allocation/de-allocation pattern than normal. Commented Jul 10, 2023 at 8:19
  • Agreed. I spent some time on networking products that were fault-tolerant/high-availability, with a 5-nines expectation, and the rules we followed were definitely not mainstream. I wasn't aware of any formality behind them, they were just 'the rules' at the time. Getting new team members to follow them, rather than the latest fad, became increasingly difficult, as the company evolved post-buyout. Eventually the high-reliability products were abandoned, as they increasingly failed to meet their expectations. Ahh, bad times...
    – jimc
    Commented Jul 11, 2023 at 15:34
3

Coding and testing to DAL A is hard enough without adding more complications. MISRA-C is a recognised industry standard coding guideline. If you're going to adopt it, then don't try to adopt other peoples' ideas of what's good or bad as well, as you will only make life harder for yourself.

As part of the certification evidence, you will need to draw up a set of coding guidelines, and show how you meet them. It's much easier if your guidelines are primarily "follow MISRA-C", rather than a collection of different people's ideas gathered from across the internet.

1

Yes, the rules are contradictory, so throw them out - not arbitrarily - until you get something logically coherent and actionable. You can't act on logically inconsistent rules, so you have to change/adapt them; no other way around it, your gut is right.

In this case I'd say compromise in favor of pointers. Beyond null reference as a possibility, pointers aren't too bad so long as all you do is dereference them. It gets more problematic when you start, say, trying to do pointer arithmetic, and keep in mind that indexing (i.e. "p[3]") is a form of pointer arithmetic, in effect. Pointer arithmetic is a problem because you can risk an out-of-bounds memory access. Without it, pointers aren't much different from a nullable reference in "safer" languages like Java. Heck, if you want to be really paranoid, wrap the pointers in a macro like "DEREF(p)", which means any pointer access NOT wrapped in that macro can be default-regarded as sus, and it's also possible to easily search for all legitimate pointer dereference usages.

If you compromise in favor of global variables, on the other hand, you very easily will create a maintainability mess because global variables pass state between things in a hidden fashion. Once you lose track in your head or a teammate loses track in their head of how the globals interact when different function calls involving them come into play, or an implementation changes, or anything like that, BAM, it will break and not only will it break, but you will have a heck of a time trying to track down the bug.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.