2

When developing and maintaining code, I add a new member to a structure and sometimes forget to add the code to initialize or free it which may later result in a memory leak, an ineffective assertion, or run-time memory corruption.

I try to maintain symmetry in the code where things of the same type are structured and named in a similar manner, which works for matching Construct() and Deconstruct() code but because structures are defined in separate files I can't seem to align their definitions with the functions.

Question: is there a way through coding to make myself more aware that I (or someone else) has changed a structure and functions need updating?

Efforts:

The simple:

-Have improved code organization to help minimize the problem

-Have worked to get into the habit of updating everything at once

-Have used comments to document struct members, but this just means results in duplication

-Do use IDE's auto-suggest to take a look and compare suggested entries to implemented code, but this doesn't detect changes.

I had thought that maybe structure definitions could appear multiple times as long as they were identical, but that doesn't compile. I believe duplicate structure names can appear as long as they do not share visibility.

The most effective thing I've come up with is to use a compile time assertion:

static_assert(sizeof(struct Foobar) == 128, "Foobar structure size changed, reevaluate construct and destroy functions");

It's pretty good, definitely good enough. I don't mind updating the constant when modifying the struct. Unfortunately compile time assertions are very platform (compiler) and C Standard dependent, and I'm trying to maintain the backwards compatibility and cross platform compatibility of my code.

This is a good link regarding C Compile Time Assertions:

http://www.pixelbeat.org/programming/gcc/static_assert.html

Edit:

I just had a thought; although a structure definition can't easily be relocated to a source file (unless it does not need to be shared with other source files), I believe a function can actually be relocated to a header file by inlining it.

That seems like a hacked way to make the language serve my unintended purpose, which is not what I want. I want to be professional. If the professional practice is not to approach this code-maintainability issue this way, then that is the answer.

user10530562
  • 149
  • 2
  • 8
  • I always catch these kinds of things in my test suite, including the memory leaks. I very rarely run into these sorts of issues though – CoffeeTableEspresso Jun 28 '19 at 15:55
  • @CoffeeTableEspresso because of my experience I haven't needed to write test suites or unit tests yet. Right now I am studying error/exception handling related issues. I'm looking at Design by Contract at the moment which is probably why the compile time assertion came to mind. – user10530562 Jun 28 '19 at 16:17
  • 1
    Well once you get more experience I'm sure you'll write more tests. – CoffeeTableEspresso Jun 28 '19 at 17:12
  • @user10530562 : if I could go back in time 25 years I would tell my younger self to *start* with unit tests; I'm not a TDD purist, but adopting a TDD approach has improved my programming life dramatically, and mitigated the need for various self-imposed conventions I adopted early on to deal with the sorts of issues you bring up here; I urge you to not wait until you are more experienced to adopt unit tests as a matter of habit -- **do it now**; then you won't need to travel back in time – landru27 Jun 28 '19 at 17:31
  • 2
    An assertion on sizeof is probably the *worst* solution – Antti Haapala -- Слава Україні Jun 29 '19 at 06:44
  • The technique referred to by Steve is commonly called [X macros](https://en.m.wikipedia.org/wiki/X_Macro) – Antti Haapala -- Слава Україні Jun 29 '19 at 06:46
  • 1
    A trick to trace all references to the structure: (temporally) rename one of the members, or the structure tag it self. Next: fix all the errors, and add code for the new member. – wildplasser Jun 29 '19 at 12:09
  • @AnttiHaapala could you explain your reasoning regarding sizeof assertions? – user10530562 Jun 29 '19 at 17:26
  • @wildplasser that's a good idea, something I've done for other purposes. Renaming a member might be better than the tag because invalidating the tag will throw errors on every function declaration and definition which is not only broader but may be so broad the compiler stops counting or may prevent reaching the desired code. Renaming a dynamically allocated member or changing its type might have the best chances of flagging the related code. Still would be handy to detect someone else changing the code for the ultra paranoid self defeatingly defensive programmer, ha – user10530562 Jun 29 '19 at 17:35
  • @landru27 Thanks, I will look into TDD more. I take every mistake I make and try to find a best practice to self impose almost religiously. When I read Programmer Foo criticize or critique programmer Bar's work, if I agree, I try to take need of Foo's viewpoint and avoid Bar's practices. In general I try to avoid things which are very commonly criticized such as the abuse of macros, or convoluted mechanisms used in lieu of standard practices to achieve obscure requirements that could be properly designed out of the program. – user10530562 Jun 29 '19 at 17:46
  • @AnttiHaapala I was trying to use X macros for a couple things recently, for declaring matched tables of arrays, enumerations, and stringized names. I got frustrated though because the compiler started invalidating the generated code and I couldn't find a logical reason why. With only 100 entries and at random entries. I switched to code generation from a csv and it worked flawlessly. I was not aware of the term "X macro" – user10530562 Jun 29 '19 at 19:15

2 Answers2

2

I've been programming in C for almost 40 years, and I don't know of a good solution to this problem.

In some circles it's popular to use a set of carefully-contrived macro definitions so that you can write the structure once, not as a direct C struct declaration but as a sequence of these macros and then, by defining the macro differently and re-expanding, turn your "definition" into either a declaration or a definition or an initialization. Personally, I feel that these techniques are too obfuscatory and are more trouble than they're worth, but they can be used to decent effect.

Otherwise, the only solution -- though it's not what you're looking for -- is "Be careful."

In an ideal project (although I realize full well there's no such thing) you can define your data structures first, and then spend the rest of your time writing and debugging the code that uses them. If you never have occasion to add fields to structs, then obviously you won't have this problem. (I'm sorry if this sounds like a facetious or unhelpful comment, but I think it's part of the reason that I, just as @CoffeeTableEspresso mentioned in a comment, tend not to have too many problems like this in practice.)

It's perhaps worth noting that C++ has more or less the same problem. My biggest wishlist feature in C++ was always that it would be possible to initialize class members in the class declaration. (Actually, I think I've heard that a recent revision to the C++ standard does allow this -- in which case another not-necessarily-helpful answer to your question is "Use C++ instead".)

Steve Summit
  • 45,437
  • 7
  • 70
  • 103
1

C doesn't let you have benign struct redefinitions but it does let you have benign macro redefinitions.

So as long as you

  1. save the struct body in a macro (according to a fixed naming convention)
  2. redefine the macro at the point of your constructor

you will get a warning if the struct body changes and you haven't updated the corresponding constructor.

Example:

header.h:

#define MC_foo_bod  \
    int x; \
    double y; \
    void *p
struct foo{ MC_foo_bod; };

foo__init.c

#include "header.h"

#ifdef MC_foo_bod
    //try for a silent redefinition
    //if it wasn't silent, the macro changed and so should this code
    #define MC_foo_bod  \
        int x; \
        double y; \
        void *p
#else
    #error "" 
    //oops--not a redefinition
    //perhaps a typo in the macro name or a failure to include the header?
#endif
void foo__init(struct foo*X)
{
    //...
}
Petr Skocik
  • 58,047
  • 6
  • 95
  • 142