MISRA-Matters Column
|
Several things have come up on C programming forums recently that made me realise that whilst some solid truths have stood the test of time so has a lot of bad practice. This is despite much information and many guidelines to the contrary. This came up in a question on a C newsgroup as to why some code had failed. It was one of the usual cases of getting “==” confused with “=” however there were various solutions to “improve” the overall functions shown. The improve is in quotes because most of it was programmers trying to be clever. This is usually an oxymoron.
One of the solutions had the line A = B == C; which many of us said should at least be
A = (B==C);
simply for clarity bearing in mind the original error was a confusion over = and ==. Some suggested that
if (B==C) { A=1; else{ A=0 }
might be clearer. However, in the online discussion there were a lot of hot under the collar programmers saying they were not going to code for novices or the weakest link and any good programmers should be able to sight read any complex lines and understand them or they were just not up to the job… It said more about their egos than Engineering skills.
However programming is not about proving how clever you are and writing complex code. After all as Brian Kernigan said:- "Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it." Over a few decades I have found this to be very true and had to debug some “clever” code. On one particular occasion many years ago I ended up rewriting some long complex lines in to multiple simple lines just to work out what they were actually doing… and in doing so found the errors. Errors that were missed by everyone in the long complex lines but glaringly obvious in the short simple lines.
The thing that still surprises me is the number of programmers who nearly two decades on still confuse space on the screen or page with the size of the binary produced.
Interestingly all three constructs mentioned above will probably produce the same binary after the compiler has optimised it. I have been told that one compiler specific uses one less byte and one cycle less for A = (B==C); than the fully expanded version. Your experiences may vary but the differences tend to be small, variable and not always in the direction you expect.
The difference is that when a complex line is broken down in to multiple less complex lines it is easier to debug. This is because when single stepping with a simulator, debugger or an ICE you can go stage by stage in the HLL. If it is all one line you have to find the assembler window and use that if you want to see what is happening inside the line.
This is partly why we re-wrote those lines all that time ago. So we could step though the C code in testing and bugging. Writing complex code usually does not only not save any time or space in the binary but often costs time and money in test, debug and maintenance.
This comes back to the title. Instead of long complex lines use multiple simple ones. Much like RISC processors and Keep It Simple Software.
When I mentioned this topic to the Chairs of the MISRA panels I got an interesting reply from Steve Frost of the MISRA Auto Code panel He said: [KISS] It's also applicable to the model-based design/auto-coding area, if not more so in some ways. On some projects we have seen, models are supplied as part of the 'requirements specification'.
When modelling for autocode generation then the model we will autocode from can be considered the design specification for the code (or part of at least. Supporting textual documentation is often still necessary and valuable.) Strategy guides and calibration guides (user documentation) can be generated from the model, or to include snapshots of the model alongside explanatory text, to explain how the autocoded application will function and can be calibrated in the field. The source models can aid the testing effort; test vectors designed at the model development stage can (should) be carried forward to code testing, to achieve a large proportion of any given code coverage metric 'for free'. However, further test vectors are very likely still required to achieve the final few percent of code coverage.
So one model needs to be understood by a number of different users:
* the model author - well, it would help with model maintenance if he can remember how a model works when he comes back to it
* the model maintainer - if different from the original author
* the end-user - via 'strategy guide' documentation that includes model snapshots.
* the calibrator - similarly.
* the test engineer - could be unit tester at code level, or functional tester at the system level
In all cases a KISS model following a clear and consistent modelling style will aid understanding/readability/maintainability/etc., just as it does for coding.
Well that is the view from MISRA-Autocode. So as you can see being clever in coding or modelling is often an oxymoron. Clean simple designs are required that are easy to test (possibly debug) and maintain. Which brings me on to another long-standing point related to the first one. The first commandment in every programmers 10 Commandments I have seen has always been “Run Lint and run it often!”
This dates back to the original K&R C where lint was part of the C/Unix compiler chain. In fact Dennis M. Ritchie said “ ….to detect legal but suspicious constructions, and to help find interface mismatches undetectable with simple mechanisms for separate compilation, Steve Johnson adapted his pcc compiler to produce lint, which scanned a set of files and remarked on dubious constructions.” Now Johnson did that in 1976 and Richite wrote about it in “The Development of the C Language” for the AMC in 1993. Yet last month I had someone telling me he used a C++ compiler to parse C code to see if all the functions and parameter calls matched! When I queried why he did not use static analysis he said that I had to remember that this was back in the mid 1990’s… that is after the quote from Ritchie talking about lint from twenty years before!
Part of the reason why MISRA only suggests rather than mandates static analysis is that a couple of companies who make static analysers have people on the MISRA C and C++ panels. They did not want any hint of commercial bias in MISRA. This is despite many studies showing that static analysis is a very effective way of removing bugs and virtually every Standard for high reliability software e.g. 61508, 26262, 50128 , Do178, FDA (medical) etc requires static analysis, and often a C subset like MISRA-C. Even the current wording for MISRA-C says “your static analyser should check for an many MISRA rules as possible”. It pre-supposes you are using static analysis. So what is your excuse for not using static analysis?
So to recap: when using the C language as invented by K&R:
K says keep it simple and
R says use static analysis and
I say use the MISRA Standards.
Eur Ing Chris Hills BSc CEng MIET MBCS MIEEE FRGS FRSA is a Technical Specialist and can be reached at This Contact
Copyright Chris A Hills 2003 -2013
The right of Chris A Hills to be identified as the author of this work has been asserted by him in accordance with the Copyright, Designs and Patents Act 1988