Mobile
My iOS apps
Other apps
Open source
  • Bandwidth benchmark
  • RAVM virtual machine
  • Big integer division
  • Prime numbers
  • AntiJOP sanitizer
  • TouchWidgets UI lib
  • Networking utils
  • Documentation
  • x86 instructions ref
  • GIT quick ref
  • GPG quick ref
  • Avoid Ubuntu
  • Android malware risks
  • iOS malware risks
  • OS/X security tips
  • Who blocks Tor
  • Software engineering
  • BASH aliases
  • I.B. pro/con
  • Vocal programming
  • Nutrition
  • Blog
  • Contact
    1 at zsmith dot co

    Object-Oriented C Programming

    Revision 7
    Copyright (C) 2008,2015 by Zack Smith
    All rights reserved.

    Introduction

    Object-oriented programming languages (OOPLs) became dominant a couple decades ago over older procedural languages like C, Pascal and the like. With these languages come fundamental problems and complexities that are not necessarily desired by a software architect.

    The case of C++ exemplifies the pitfalls of using OO languages: its templates and multiple inheritance and ever-shifting class libraries make software design, implementation, and debugging more difficult than it needs to be. OOP was never meant to be as much of a mess as C++. While some C++ code is well-written, some C++ code is an illegible tangle of derived classes worse than any "goto" based spaghetti code from the dawn of computing. And yet, nevertheless a typical C++ partisan will defend such a C++ tangle as being perfectly valid and appropriate. Of course he would: Personal job security is the only motive to supporting such gunk.

    Due to the problems of common some object-oriented languages, it is sometimes preferable for a programmer to use a non-OOP language such as C augmented to use object-oriented practices.

    Key techniques of object-oriented C programming

    Struct with pointers

    A simple technique for implementing an object in C is to imitate C++, and provide a struct with pointers to methods inside.
    typedef struct obj {
        int a;
        char *b;
    
        // Class methods
        struct obj (*new)();
        void (*delete)(struct obj *);
    
        // Object methods
        void (*set_a)(struct obj* self, int);
        void (*set_b)(struct obj* self, char *);
    } Object; 
    
    In use:
    Object *obj = class_struct->new();
    obj->set_a (123);
    
    A possible pitfall of this approach is that methods in the struct might be NULL due to lack of initialization or may have been erroneously overwritten, in either case resulting in a program crash when used. And if a function pointer is overwritten, there will be no easy way to ascertain where the pointer was overwritten.

    Struct and message-passer

    A better alternative is to use an approach similar to Objective C's, and pass messages to objects based on a method name.
    typedef struct obj {
       int class_id;
       int a;
       char *b;
    } Object;
    
    A send_message function is used to send messages to objects and is implemented to take variable numbers of arguments. The class initializer is responsible for loading all of its methods into a hash of functions. Note that it's necessary to include "stdarg.h".
    int send_message(int count, ...)
    {
       int tmp;
       va_list ap;
       va_start(ap, count);
       Object *this = va_arg(ap, Object*);
       char *msg = va_arg(ap, char*);
    
       // Here, look up method in hash and call appropriate function...
    
       va_end(ap);
    } 
    
    In use:
    Object *obj;
    obj = send_message (class_struct, "new");
    send_message (obj, "set_a", 123);
    
    Of course, in practice "send_message" would be shortened to something more useable, e.g. "S". In Objective C, one simply encloses the message passing in brackets e.g.
    [ object message: parameters ];
    

    An advantage to this approach is that method pointers are less likely to be overwritten since they are not in the data struct.

    This approach can also be extended to include the hashing of object data, to provide further protection against program errors.

    A further refinement might be to include some non-textual predefined numeric method names, e.g. implemented using enum or #define, which index into a table to avoid doing any string comparison. For instance:

    enum { 
       MESSAGE_NEW=1, 
       MESSAGE_DESTROY=2, 
       MESSAGE_DUMP=3 
    };
    Object *obj = send_message (rectangle_class_struct, MESSAGE_NEW);
    send_message (obj, "set_width", 456);
    

    Container classes

    In C++, the template functionlity permits the creation of containers of objects, for instance an ordered list of string objects. However the use of templates can quickly make code unreadable.

    In C, an equivalent functionality can be implemented by simply having all containers deal with only one object type, which is a 32-bit or 64-bit value, and by providing the container with any functions that it may need to process that value, e.g. to compare two of them in order to sort a list. The application logic would then have to deal with the values themselves to decide if it is a basic type, e.g. int or float, or an object pointer.

    An alternative is to literally pound-define the type in question e.g. #define SOMETYPE int, just before pound-including the container class, e.g. write the container class to contain things of type SOMEOBJECT, which is defined in another file.

    Downloads

    My implementation of the message-passing approach is temporarily offline.



    © Zack Smith