2

let's say you have a function that set an index and then update few variables based on the value stored in the array element which the index is pointing to. Do you check the index to make sure it is in range? (In embedded system environment to be specific Arduino) So far I have made a safe and unsafe version for all functions, is that a good idea? In some of my other codes I noticed that having only safe functions result in checking conditions multiple time as the libraries get larger, so I started to develop both. The safe function checks the condition and call the unsafe function as shown in example below for the case explained above.

Safe version:

bool RcChannelModule::setFactorIndexAndUpdateBoundaries(factorIndex_T factorIndex)
{
    if(factorIndex < N_FACTORS)
    {
        setFactorIndexAndUpdateBoundariesUnsafe(factorIndex);
        return true;
    }

    return false;
}

Unsafe version:

void RcChannelModule::setFactorIndexAndUpdateBoundariesUnsafe(factorIndex_T factorIndex)
{
    setCuurentFactorIndexUnsafe(factorIndex);
    updateOutputBoundaries();
}

If I am doing it wrong fundamentally please let me know why and how I could avoid that. Also I would like to know, generally when you program, do you consider the future user to be a fool or you expect them to follow the minimal documentation provided? (the reason I say minimal is because I do not have the time to write a proper documentation)

void RcChannelModule::setCuurentFactorIndexUnsafe(const factorIndex_T factorIndex)
{
    currentFactorIndex_ = factorIndex;
}
too honest for this site
  • 12,050
  • 4
  • 30
  • 52
Ali
  • 139
  • 1
  • 2
  • 11
  • 2
    The "unsafe" version is perfectly safe if you control how the index is set. – Pete Becker Dec 06 '17 at 20:02
  • "by controlling how the index is set", you mean checking it range? If yes, I was hoping not to do that due to performance related issue. If there is another way, could you elaborate? – Ali Dec 06 '17 at 20:04
  • Oh you mean control from outside? If that, I am sure I can do it but what about future users, do I have to worry about them? – Ali Dec 06 '17 at 20:08
  • 3
    If performance is critical, why is checking the index every time it's used better than checking it when it's set? What is the overhead of checking when the index is set, and how can that be a bottleneck in code that uses this? – Pete Becker Dec 06 '17 at 20:08
  • Have you established yet that using unsafe functions actually delivers material performance increases? – Robert Harvey Dec 06 '17 at 20:09
  • 1
    Are you writing a reusable library? Because otherwise, you have complete control of the arguments passed to your functions. – John Bollinger Dec 06 '17 at 20:09
  • Actually the unsafe version does not check anything. It just assume the index is correctly given by the user. When it sets the index, it just sets it. – Ali Dec 06 '17 at 20:09
  • I believe Pete was referring to how the factorIndex_T is initialized/manipulated. If you perform the checks there then whatever takes a factorIndex_T has already been validated. Depending on the nature of the code I might go this safe/unsafe route (or safe only for that matter) or I might use asserts but then have the release mode code be only the unsafe path. – SoronelHaetir Dec 06 '17 at 20:10
  • I wouldn't provide any "safe" APIs unless my code has a higher privilege than the caller (i.e. when I have to check). Otherwise documenting the preconditions should be sufficient. – rustyx Dec 06 '17 at 20:11
  • @RobertHarvey In a GLCD library where the functions was called many times every second (162*100) times, it made a significant difference. – Ali Dec 06 '17 at 20:12
  • @JohnBollinger Yes, I want a reusable library. – Ali Dec 06 '17 at 20:13
  • @RustyX I am actually hoping that this library become an arduino library were other people use it and some arduino users may not have strong programming background – Ali Dec 06 '17 at 20:18
  • 2
    In that case, an argument-checking version of each external function perhaps makes sense, with non-argument-checking implementations of those and all internal-only functions. If you perform argument checking then do it (only) at the boundary between your library and the client code. But it's pointless to offer a choice to your users, for if you want to protect them from usage errors then you cannot rely on them to choose the "safe" versions of your functions. – John Bollinger Dec 06 '17 at 20:20

3 Answers3

4

Safety checks, such as array index range checks, null checks, and so on, are intended to catch programming errors. When these checks fail, there is no graceful recovery: the best the program can do is to log what happened, and restart.

Therefore, the only time when these checks become useful is during debugging and testing of your code. C++ provides built-in functionality for dealing with this through asserts, which are kept in the debug versions of the code, but compiled out from the release version:

void RcChannelModule::setFactorIndexAndUpdateBoundariesUnsafe(factorIndex_T factorIndex) {
    assert(factorIndex < N_FACTORS);
    setCuurentFactorIndexUnsafe(factorIndex);
    updateOutputBoundaries();
}

Note: [When you make a library for external use] an argument-checking version of each external function perhaps makes sense, with non-argument-checking implementations of those and all internal-only functions. If you perform argument checking then do it (only) at the boundary between your library and the client code. But it's pointless to offer a choice to your users, for if you want to protect them from usage errors then you cannot rely on them to choose the "safe" versions of your functions. (John Bollinger)

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • So if a user uses the library, how can he choose between debugging version or released version? Sorry if it is a noob questions but I am not very familiar with assert yet. – Ali Dec 06 '17 at 21:34
  • I beg to differ. When dealing with the external requests you must safety check them. A graceful recovery would be to report an error to the requestor. – user58697 Dec 06 '17 at 21:35
  • @Ali You can provide two versions - one compiled with diagnostics turned on, and another one with diagnostics off. – Sergey Kalinichenko Dec 06 '17 at 21:45
  • @dasblinkenlight great idea thank you. just if you do not mind edit John Bollinger comment in your answer so it becomes a more complete answer. "an argument-checking version of each external function perhaps makes sense, with non-argument-checking implementations of those and all internal-only functions. If you perform argument checking then do it (only) at the boundary between your library and the client code. But it's pointless to offer a choice to your users, for if you want to protect them from usage errors then you cannot rely on them to choose the "safe" versions of your functions." – Ali Dec 06 '17 at 21:50
  • I think your answer and his answer are both interesting based on the circumstances. But again you both have more experience than me so please be the judge. – Ali Dec 06 '17 at 21:52
  • @user58697 This depends on the environment. Ways to communicate an error in embedded environments are very limited during debugging, and virtually non-existent in production. Requestors cannot recover from their own programming errors, so there is no point to report these kinds of errors to them. Of course, non-programming errors (e.g. configuration or environment errors) need to be reported back to the caller. – Sergey Kalinichenko Dec 06 '17 at 21:56
  • In my scenario requestor is an _external_ entity, such as a host, or a TCP client for which the embedded system serves. It is beyond the scope of an embedded system how the _requestor_ deals with errors. The embedded system must protect itself from possibly malicious requests. – user58697 Dec 06 '17 at 22:00
0

Do you make safe and unsafe version of your functions or just stick to the safe version?

For higher level code, I recommend one version, a safe one.

High level code, with a large set of related functions and data, the combinations of interactions of data and code are not possible to fully check at development time. When an error is detected, the data should be set to indicate an error state. Subsequent use of data within these functions would be aware of the error state.


For lowest level -time critical routines, I'd go with @dasblinkenlight answer. Create one source code that compiles 2 ways per the debug and release compiles.

Yet keep in mind @pete becker, it this really likely a performance bottle neck to do a check?


With floating-point related routines, use the NaN to help keep track of an unrecoverable error.


Lastly, as able, create functions that do not fail and avoid the issue. With many, not all, this only requires small code additions. It often only adds a constant of time performance penalty and not a O(n) penalty.

Example: Consider a function to lop off the first character of a string - in place.

// This work fine as long as s[0] != 0
char *slop_1(char *s) {
  size_t len = strlen(s);         // most work is here
  return memmove(s, s + 1, len);  // and here
}

Instead define the function, and code it, to do nothing when s[0] == 0

char *slop_2(char *s) {
  size_t len = strlen(s);
  if (len > 0) {                // negligible additional work
    memmove(s, s + 1, len);
  }
  return s;
}

Similar code can be applied to OP's example. Note that it is "safe", at least within the function. The assert() scheme can still be used to discovery development issues. Yet the released code, without the assert(), still checks the range.

void RcChannelModule::setFactorIndexAndUpdateBoundaries(factorIndex_T factorIndex)
{
    if(factorIndex < N_FACTORS) {
      setFactorIndexAndUpdateBoundariesUnsafe(factorIndex);
    } else {
      assert(1);
    }
}
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • Does memmove really do something stupid like actually reading/writing stuff if len==0? – DisappointedByUnaccountableMod Dec 06 '17 at 23:56
  • @barny `memmove(s, s + 1, 0);` has specified behavior. Nothing read, nothing written, but the pointers must be valid. `s+1` is not for `char *slop_1()` when `len==0`. IIRC, the one passed rule does not help here. See https://stackoverflow.com/q/25390577/2410359 . It appear debatable. Best to avoid. – chux - Reinstate Monica Dec 07 '17 at 00:14
0

Since you tagged this Arduino and embedded, you have a very resource-constrained system, one of the crappiest processors still manufactured.

On such a system you cannot afford extra error handling. It is better to properly document what values the parameters passed to the function must have, then leave the checking of this to the caller.

The caller can then either check this in run-time, if needed, or otherwise in compile-time with a static assert. Your function would however not be able to implement it as a static assert, as it can't know if factorIndex is a run-time variable or a compile-time constant.


As for "I have no time to write proper documentation", that's nonsense. It takes far less time to document this function than to post this SO question. You don't necessarily have to write an essay in some Word file. You don't necessarily have to use Doxygen or similar.

But you do need to write the bare minimum of documentation: In the header file, document the purpose and expected values of all function parameters in the form of comments. Preferably you should have a coding standard for how to document such functions. A minimal documentation of public API functions in the form of comments is part of your job as programmer. The code is not complete until this is written.

Lundin
  • 195,001
  • 40
  • 254
  • 396
  • I am not being paid for what I am doing and hope to make the code reusable by others considering the amount of effort I have put in this. The project is currently 18000 lines of code with many libraries. Eventually I may make it open source partially or fully and to me proper documentation is not in code comments, because that would never help an average Arduino user who is going to be my code target audience/user. – Ali Dec 07 '17 at 14:08