Prefer type definitions (typedef
) to macro definitions (#define
) when encoding types. Type definitions obey scope rules; macro definitions do not. textual substitution is inferior to using the type system. While type definitions for non-pointer types have similar advantages [Summit 2005], can make it more difficult to write const
-correct code (see DCL05-C. Use typedefs of non-pointer types only).
Noncompliant Code Example
This noncompliant code example will not compile, because macros use textual substitution and not the type system:
#define MATRIX double matrix[4][4] MATRIX matrix_a;
After preprocessing, this code example is translated to the following invalid declaration:
#define MATRIX double matrix[4][4] double matrix[4][4] matrix_a;
Compliant Solution
Using type definitions instead of macro definitions in this compliant solution results in a valid declaration:
typedef double matrix[4][4]; matrix matrix_a;
Noncompliant Code Example
I don't actually know what is wrong with this:
#define uchar unsigned char
Compliant Solution
Use type definitions to encode all non-pointer types.
typedef unsigned char uchar;
Risk Assessment
Recommendation | Severity | Likelihood | Remediation Cost | Priority | Level |
---|---|---|---|---|---|
PRE03-C | Low | Unlikely | Medium | P2 | L3 |
Automated Detection
Tool | Version | Checker | Description |
---|---|---|---|
Axivion Bauhaus Suite | 7.2.0 | CertC-PRE03 | |
1.2 | CC2.PRE03 | Fully implemented | |
Helix QAC | 2024.4 | C3413 | |
LDRA tool suite | 9.7.1 | 79 S | Enhanced Enforcement |
Related Vulnerabilities
Search for vulnerabilities resulting from the violation of this rule on the CERT website.
Related Guidelines
SEI CERT C++ Coding Standard | VOID PRE03-CPP. Prefer typedefs to defines for encoding types |
ISO/IEC TR 24772:2013 | Pre-processor Directives [NMP] |
15 Comments
David Svoboda
This might be enforceable by a static analyzer that could parse macro definitions and see that they refer to a valid typedef. ROSE can't currently do this, so we don't know how effective or difficult this would be.
Masaki Kubo
in
typedef char *NTCS;
const NTCS p = &data;
Isn't p a "pointer to a const char" not "a constant pointer to char"?
David Svoboda
No, p is a const pointer to char. If the code had used a macro instead of a typedef:
Then p would be a pointer to a const char. The upshot is that typedefs are treated differently than macros (which is the focus of this rule.)
These code snippets should prob be examples in the rule as well, although I'm not sure that one is 'noncompliant' while the other is 'compliant'.
Martin Sebor
While I agree with this rule in principle, in my opinion using a
typedef
forchar*
or any other pointer type is ill-advised because:const
orvolatile
qualifiers to thetypedef
has an unexpected effect (as mentioned in second paragraph of the recommendation and evidenced by the comment by Masaki Kubo above)Both of these issues can lead to subtle and difficult to diagnose bugs. For example:
Perhaps instead of using examples involving pointers it might be better to use structs instead. I.e., instead of the following non-compliant solution:
use the following compliant solution:
David Svoboda
The problem with using typedefs on pointers is more the confusion between
and
Perhaps we need a rule about that, but it should be distinct from this rule.
If you read it in English, cstring is a pointer to char, and so const cstring is a const pointer to char. Then the compiler's behavior on your code samples becomes obvious. (The tricky one is 'const char*' which is 'a pointer to const char'.
Furthermore, your suggestion of using structs for the NCCE/CCE fail, because macros and typedefs behave the same (both are accepted by the compiler:
OTOH I'm not enamored of this rule, as the NCCE violates DCL04. Unless we can find a NCCE that does not violate another rule, I'm not sure this rule should live on in its current form. Perhaps it should die, or be replaced by the "const char* vs char* const" rule suggested above. What do you think?
Martin Sebor
I think the rule is sound and worthwhile as is. My objection is only to the the code examples. Incidentally, as I just noted in a comment Re: API03-C. Create consistent interfaces and capabilities across related functions, the problem with using typedefs for pointers is exemplified in the interfaces of The Managed String Library [Burch 06].
Martin Sebor
After thinking about this as well as first hand experiencing the same design flaw as the one that affects The Managed String Library discussed above, I'd like to change this guideline to:
unsigned char
orlong double
as an example wheretypedef
may be called for, e.g., instead oftypedef
for pointers and give the interface of the Managed String Library as a Noncompliant Code Example.If no one objects I'll go ahead and make these changes.
Robert Seacord (Manager)
no objections from me. I'm thinking the pointers should be given as an exception to this rule, because otherwise the two rules would contradict each other. You could also build it right into the title,e.g.:
PRE03-C. Prefer typedefs to defines for encoding types other than pointers
In this case, rather than exception you could provide a NCE using the managed string library and then showed the preferred solution. After that, I should probably go back and fix the library. 8^(
Aaron Ballman
I would like to remove the DCL05-C issue with the CCE, but am struggling to come up with a compelling code example. It would be trivial to use:
But the existing code example is better at showing the dangers of using the macro over the typedef. Ideas welcome!
David Svoboda
Well, to comply with DCL05-C, you prob should limit this rule to not apply to pointers. You could make a NCCE involving structs or unionts or arrays. And it could involve pointers (just not a toplevel pointer such as it has now)
Jeffrey Bian
This is not confusing at all. An personally I prefer putting const storage class specifier on the right of type.What typedef does, unlike #define that only expands the text, is to make a type alias.
So consider that :
const NTCS p;
is equivalent to
NTSC const p;
The latter one is much more consistent ans easy to understand and will never cause confusion as read as "const pointer to char".
David Svoboda
If NTCS is not a pointer type, then it doesn't matter where you put the const. If it is a pointer type, then it does matter. Putting the const after it renders the pointer const, but not the pointed-to data. Likewise, putting the const before NTCS renders the pointed-to-data const (it can't be changed by the pointer) but the pointer itself could be changed to point somewhere else.
Jeffrey Bian
Yup. I was referring to the specific case above (typedef char * NTSC).
I mean by always having const storage class on right to the type, it will help enhance the readability and reduce the confusion, especially in production quality code. It also makes an obviously tip that : const storage class decorates the closest type left to it.
Justin Loo
Is it necessary to split the CS into:
Testing under gcc 4.6.3, I got the correct result with just:
While this violates DCL04-C, I think that this better demonstrates the differences between typedef and #define behavior since it more closely mirrors the NCCS. As it is, the correct behavior of the CS could be incorrectly attributed to the declarations being on separate lines.
Aaron Ballman
It is not strictly necessary to split the CS into multiple lines.
We try to ensure all of our coding examples (especically CSes) comply with our rules and recommendations. Since it would violate DCL04-C. Do not declare more than one variable per declaration, it was likely written that way.
However, you do bring up a good point that the problem in the NCCE could also be solved in exactly the same way (splitting up the declarations). I'm on the fence as to whether the problem here is with the NCCE's formatting (esp since it violates more than one guideline) or the CS, or the example as a whole.