Dan_Saks

image
President

Dan Saks is president of Saks & Associates, a C/C++ training and consulting company. For more information about Dan Saks, visit his website at www.dansaks.com. Dan also welcomes your feedback: e-mail him at dan@dansaks.com.

Dan_Saks

's contributions
Articles
Comments
    • Understanding how to implement virtual functions in C is useful for a variety of reasons, one of which is to see why you should be using C++ instead of C.

    • Pure virtual functions provide a way to avoid defining base class member functions that have no meaningful implementation.

    • Virtual function calls work properly only if the vptrs and vtbls are initialized properly. C++ does this automatically, while C makes you do it manually.

    • Once more unto the breach, Dan tries to dispel the notion that C++ is a purely object-oriented language in which all classes must use virtual functions.

    • Although C doesn’t provide native support for virtual functions, you can emulate virtual functions in C if you attend to all the details.

    • Although over two thirds of embedded systems developers are programming 32- and 64-bit processors, C is surprisingly still more commonly used than C++.

    • Dan Saks crunches over 12 years of data on programming-language preferences from reader studies and finds some surprises about C/C++.

    • Class derivation and virtual functions let you define a common interface for related classes with similar, yet distinct, behaviors.

    • Discriminated unions can be useful in some applications, but they also provide insight into the advantages of using virtual functions.

    • Writing better hardware interfaces may require writing fairly elaborate declarations. It's probably worth the effort.

    • Declaring operator new as a class member can be a handy way to provide guaranteed initialization for memory-mapped objects.

    • Using member initializers offers more control over what constructors do, and helps eliminate unnecessary default initialization.

    • Often when it seems that C++ is generating bigger and slower code than C, it may be that C++ is actually just distributing generated code differently.

    • Even the experienced C++ programmer can be confused about what exactly constructors do and when they get called.

    • Some programmers think modeling memory-mapped devices as C++ objects is too costly. With some architectures, the chances are they're wrong.

    • If accessing memory-mapped device registers via pointers or references is allegedly slow, then what alternatives might be faster?

    • Dan Saks, a widely recognized expert in C and C++, offers tips and techniques to embedded developers.

    • Here's a simple technique to generate a warning that your C++ code is using dynamic allocation against your wishes.

    • Different dialects of C and C++ support different forms of flexible array members. Unfortunately, the more expressive forms are the less portable ones.

    • Engineering is supposed to be applied science. Maybe we're overlooking opportunities to apply science more often.

    • Here's how to avoid the undefined behavior that arises when using array allocation and deallocation functions in C on a machine with strictly aligned types.

    • Maybe the Software Development Conference had just run its course. Still, I'm going to miss it, and a lot of others will, too.

    • The distinction between allocating arrays and allocating raw storage is useful and worth preserving whether you program in C or C++.

    • Has anyone out there tried to do what I've been suggesting and found that it caused more problems than it solved?

    • Why might you prefer to write low-level code in C++ rather than in C? You've got much to gain and little or nothing to lose.

    • The new draft standard for C++ offers a safe and convenient way to keep your enumeration types slim and trim.

    • C and C++ both use "void *" as the generic data pointer type, but they treat it differently. Here's some insight into why.

    • Dan Saks looks back on his ten years as a columnist for Embedded Systems Design (and Embedded Systems Programming) magazine.

    • Scope determines what you can see within a translation unit. Linkage determines what you can see across translation units.

    • In C and C++, the unusual nature of char--that it's distinct from its signed and unsigned cousins, but not completely so--leaves many programmers puzzled about when to use plain char in preference to an explicitly signed or unsigned char.

    • Storage class specifiers don't specify scope but combine with scope to determine storage duration. Here's the second part in a series on scope, storage allocation, and linkage.

    • Writing highly portable code in C or C++ is possible, but not always as easy as we'd like it to be.

    • Many programmers have difficulty distinguishing the concepts of scope, storage allocation, and linkage. Dan begins the process of sorting them out.

    • Using size_t may be awkward for some programmers, but using it still solves more problems than it creates.

    • Using size_t appropriately can improve the portability, efficiency, or readability of your code. Maybe even all three.

    • Exception handling in C++ provides the simplicity of error handling with longjmp, but without the leaks.

    • Embedded systems developers use so many terms in so many different ways, it's surprising that we understand each other as well as we do.

    • Just as you can often treat device registers as a memory-mapped struct, you can treat an interrupt vector as a memory-mapped array.

    • It's almost impossible to write real programs, especially embedded ones, without using a cast here or there. Nonetheless, you should try to use casts as sparingly as you can.

    • How you define pointers to memory-mapped device registers can have an impact on the efficiency of your device drivers.

    • Memory-mapped I/O is something you can do reasonably well in standard C and C++.

    • Sometimes the only way to catch your coding mistakes is by thoughtful critique from others.

    • Class constructors guarantee object initialization. Const member functions protect objects from spurious changes.

    • C++ classes use the keywords public and private to preserve the integrity of abstractions more effectively than anything you can do with structs in C.

    • Incomplete types eliminate some of the pitfalls associated with data abstraction in C, but they can also create other problems.

    • Classes are the most useful feature that C++ offers over C. This month Dan shows you why this is so and how to approximate classes in C.

    • There are a whole lot of pluses to C++. If you're already using C, the leap up is not as large as you might think.

    • Here are some additional tips for using enumerations as loop counters and for displaying enumeration names.

    • It's mailbag time. In response to reader feedback, Dan takes a moment to clarify some nuances of using enumerations.

    • Whether you use enumerations to count up or down, be careful at the boundaries. Values just beyond the ends must have valid representation.

    • Loops that step through enumerations can be very handy. With a little thought, you can make them clearer and more robust.

    • C treats tags as second class types. C++ isn't much kinder. Here's how to give them first-class treatment in both languages.

    • Overcomplex statements can confuse your compiler. Knowing where the sequence points are can help make your intentions clear.

    • The C and C++ standards do not specify the order of evaluation for function arguments. This can lead to subtle portability problems.

    • Both the C and C++ Standards leave gray areas in the language. If you don't know why, those gray areas can look like black holes.

    • While symbolic constants will help your code, you can overuse them. Symbolic constant expressions can be just as useful, but without the clutter.

    • There's more than one way to define symbolic constants in C and C++. It helps to know what all of your choices are.

    • The rules for initializing references make passing by reference-to-const an efficient and attractive alternative to passing by value.

    • The rules for initializing references resemble the rules for initializing pointers. But they do differ. And you should know how.

    • Lvalues actually come in a variety of flavors. If you really want to understand how compilers evaluate expressions, you'd better develop a taste.

    • C and C++ enforce subtle differences on the expressions to the left and right of the assignment operator

    • Ever stop to think about what a data type really is? If you haven't, maybe you should, especially if you want to use operator overloading.

    • Knowing how references really differ from pointers should help you decide when to use references and when to stick with pointers.

    • Just as you can use the const qualifier in pointer declarations, you can also use it in reference declarations-with one notable exception.

    • In C++, references and pointers have overlapping functionality. Here are some insights to help you decide which to use for a particular task.

    • Every literal has a type. It may not be obvious, and it may vary across platforms, but the standard does specify it precisely.

    • By using the same name for two or more functions that perform essentially the same operation, you can make your programs more readable and easier to write.

    • Good embedded programming technique is just plain good programming technique.

    • In reply to items (1) and (2): It's not only possible, but in fact often preferable, to address your concerns by layering polymorphic types over non-polymorphic types. I'm planning to show this in a future column. In reply to the last paragraph: As I explained in a comment last November, Bjarne Stroustrup designed C++ as a multi-paradigm language. One of those paradigms is indeed OO. But C++ also supports procedural, object-based, and generic programming, among others. Perhaps you view multi-paradigm techniques as "not instructive or good advice" because they don't hew to your notion that C++ should be used only as an OO language in which all classes are polymorphic. Yet you recognized earlier that implementing classes for memory-mapped devices as polymorphic types causes them to fail. I don't think you can't have it both ways: you can't insist on using C++ in limited ways that don't work and then blame C++ for failing, especially since C++ offers ways that do work.

    • willc2010: "The basic problem here is that C++ (and OO in general) confuses reuse with polymorphism." It's one thing to admit you're confused and ask for clarification. It's quite another to assert that C++ and OO are causing the confusion, when not everyone is confused. C++ supports several different approaches to reuse. Reuse via run-time polymorphism (virtual functions) is one. Reuse via genericity (templates) is another. This is probably a good topic for a future column.

    • cdhmanning: "This is exactly the point I was raising with the proper use of abstraction in previous articles. A C++ interface to a timer or UART or whatever should always be through an abstract base class." I presume you're referring to the comments you posted regarding my article entitled "Using member new to map devices" http://www.eetimes.com/discussion/4230743 . I'm usually reluctant to use words such as "right" and "wrong" when discussing judgments about programming style and idioms. But in this case, you're advocating an extremely rigid rule that's so unhelpful in so many situations, it's a reasonable engineering approximation to simply call it "wrong". I'll explain in detail in a future column. In the meantime, I refer you to Bjarne Stroustrup's C++ Style and Technique FAQ, http://www2.research.att.com/~bs/bs_faq2.html , specifically "Why are member functions not virtual by default?" and "Why are destructors not virtual by default?"

    • Ateocinico: I'd very much appreciate seeing specific code snippets that compile under 4.6.3 but not under 4.7.0. This request is probably off-topic for this thread, so you can write to me directly at dan@dansaks.com.

    • Thanks for the positive feedback. The shape class should indeed be an abstract base class. I'm planning to cover such additional details in a future column.

    • That 0x12 should have been just 12. Thanks for catching the typo.

    • Except that it's difficult to do exactly the same thing in C and C++. A C version that's just as abstract as the corresponding C++ will rarely be as efficient as C++, and likely be less efficient. A C version that's just as efficient as C++ will likely be less abstract.

    • I agree that it's better to stick with standard language features if you can. I usually point that out, but forgot to this time. Thanks for the reminder.

    • How is what you are proposing different from the class I showed?

    • A class like my timer_type simply adds an abstraction layer around what you call the "CSR area". I don't see any problem combining my approach with yours. I'm planning to show this sometime in the future.

    • If I understand you correctly, you're suggesting that the whole point of C++ is to support hierarchies of base and derived classes with virtual functions, and that any code that doesn't employ that paradigm is an abuse of C++. I don't know any expert C++ programmer who agrees with that position. According to Bjarne Stroustrup, the designer of C++, "C++ is a multi-paradigm programming language that supports Object-Oriented and other useful styles of programming. If what you are looking for is something that forces you to do things in exactly one way, C++ isn't it. There is no one right way to write every program..." The techniques I've presented in recent columns have worked with real hardware precisely because they don't use virtual functions. The C++ Standard provides formal support for these techniques by defining categories of classes such as "standard layout classes", something I'll explain in a future column.

    • "Are these real snippets from working code?" I edited my working code slightly to remove unneeded details. But the published code is still awfully close to the original. "Don't you need a volatile?" It's on the first line of code in the article. In most desktop browsers, you can use Edit/Find or Ctrl-F to search.

    • "... usually this version of the allocator does nothing, but that is not guaranteed." Actually, regarding the placement allocation and deallocation functions, the C++ standard states: "These functions are reserved, a C++ program may not define functions that displace the versions in the Standard C++ library."

    • As noted in the article, using member initializers with members of class types avoids unnecessary default initialization, and thus tends to be more efficient. Members of const and reference types must be initialized using member initializers. Statements in the constructor body won't do. For other members, it's a style preference. I don't believe there's a performance advantage one way or the other. Member initializers provide a uniform notation that works for all types. Unless a particular initialization requires a computation too complex to fit neatly, I'd use a member initializer.

    • I'm not familiar with that reinterpret_cast either. It's not what I wrote. The website eats angle brackets whether they're in articles or comments. You're proposed corrections are what should be in the article and I'll try to get it fixed.

    • Yes, C99 does indeed have inlining, but you can't use it to implement "encapsulation by separate compilation" without compromising the encapsulation. That is, you can't use inlining unless you declare the "encapsulated" data with external linkage in the same header as the inline function definitions, which in turn, eliminates compile- or link-time enforcement of the encapsulation. This is very similar (though not identical) to the problem I described in "Incomplete types as abstractions" at http://www.embedded.com/columns/programmingpointers/16100434. (It's under the subheading "A possible loss of performance".) As that article points out, it doesn't matter whether you use inlining as in C++ or in C99 (they are a bit different), or even whether you implement inlining using function-like macros in C89.

    • As I have written in the past, I believe C++ is generally preferable to C for most applications, embedded or otherwise. I believe that some of the remarks in this thread recommending C over C++ are mistaken. See the details in my latest column at http://www.embedded.com/columns/programmingpointers/219401085

    • Yes, they have the same meaning. For an explanation, please read any or all of the articles listed at the end of this article.

    • ptrdiff_t doesn't hold pointers -- it holds differences between pointers. On most platforms, a pointer and pointer difference have the same size, but as far as I can tell, no such guarantee appears in the standard. Nonetheless, you raise a valid point about using the %ld format specifier -- casting size_t to long might truncate the result. In practice, I think you'll be hard pressed to find a compiler where this actually will be a problem, but the possibility of truncation does exist. Using the %lld format and a cast to long long, as in: printf("%lld", (long long)d); might be less likely to cause a truncation, but still offers no guarantee. A Standard C implementation may define ptrdiff_t as some implementation-defined integer type larger than long long. I'm not aware of any compiler that does that, but again, it's possible. I think PRIdMAX is a more appropriate format specifier than PRIdPTR. If you use PRIdMAX, then you should use it in conjunction with a cast to intmax_t, as in: printf("%" PRIdMAX, (intmax_t)p); For those unfamiliar with these symbols... PRidMAX is a macro defined in the C99 header and intmax_t is a typedef defined in the C99 header . Unfortunately, not all C compilers provide these headers. I will probably elaborate on this discussion in an upcoming column. Message was edited by: SRambo