writing object like code with c in 8-bit system

Thread Starter

bug13

Joined Feb 13, 2012
2,002
Hi guys

A while a ago I asked about using C++ on a 8bit system, so the answer I got is no. But I really can see the benefit of using object like code, so I look around and fount out I can do something like that in C.

So what do you guys think if I write code something like these:
Code:
typedef uint8_t (*func_ptr1_t)(void);
typedef uint8_t (*func_ptr2_t)(uint8_t);
typedef uint8_t (*func_ptr3_t)(uint8_t, uint8_t);

typedef struct{
  uint8_t *buff;
  ...
  uint8_t var1;
  uint8_t var2;
  uint16_t var3
  uint16_t var4;
  ...;
  func_ptr1_t method1;
  func_ptr2_t method2;
  func_ptr3_t method3;
  ...;
}obj_t;

void obj_init(obt_t object, uint8_t *buff, func_ptr1_t method1, ...){
  object.buff = buff;
  objcet.var1 = default1;
  object.var2 = default2;
  ...;
  object.method1 = method1;
  object.method2 = method2;
  ...;
}


// main code
valitale uint8_t buffer[some_size];
valitale obj_t some_obj;
main(void){

  obj_init(some_obj, buffer, func_1, func_2, ...)
  while(1){
    some_obj.method1();
    ...;
  }
}
 

WBahn

Joined Mar 31, 2012
30,060
You will have many of the same issues when targeting a resource-starved architecture.

Object-oriented programming basically represents a tradeoff in which implementation-level complexity and efficiency is sacrificed in exchange for programmer-level simplicity and efficiency.

If you want to write C code that is object-like (which is how I generally program in C), then you want to pass pointers to objects (structures) and not the objects themselves. This is particularly the case in resource-starved situations because you generally have neither the time nor the space to be passing around copies of structures. Then you want to only make the structure typedef visible to the using code.

Much of your code above makes no sense. You pass an object (by value) into obj_init() which means that it is working with a copy of the structure. That copy ceases to exist when the function returns.

I have no idea what "valitale" means. Are you sure you don't mean "volitale"?
 

Thread Starter

bug13

Joined Feb 13, 2012
2,002
You will have many of the same issues when targeting a resource-starved architecture.

Object-oriented programming basically represents a tradeoff in which implementation-level complexity and efficiency is sacrificed in exchange for programmer-level simplicity and efficiency.

If you want to write C code that is object-like (which is how I generally program in C), then you want to pass pointers to objects (structures) and not the objects themselves. This is particularly the case in resource-starved situations because you generally have neither the time nor the space to be passing around copies of structures. Then you want to only make the structure typedef visible to the using code.

Much of your code above makes no sense. You pass an object (by value) into obj_init() which means that it is working with a copy of the structure. That copy ceases to exist when the function returns.
The reason I want to use object C like code is I find it a lot more readable, and easier to manager. I just need to put everything in same object in the same source file (.c and .h), and I just need to deal with the object(struct) everywhere in the code. I can still use not object like C when efficiency is more important. (or even a few lines of assembly)

How about this?
Code:
void obj_init(obt_t* object, uint8_t *buff, func_ptr1_t method1, ...){
  (*object).buff = buff;
  (*objcet).var1 = default1;
  (*object).var2 = default2;
  ...;
  (*object).method1 = &method1;
  (*object).method2 = &method2;
  ...;
}

void method1(void){
  // do stuff...
}

void method1(void){
  // do stuff
}
 

Thread Starter

bug13

Joined Feb 13, 2012
2,002
If you want to write C code that is object-like (which is how I generally program in C)...
I am quite interested in how you do it, if you have a few minutes to share :), I am simply in the process of finding my way around this object like C thing, and hopefully it will do what I need.
 

WBahn

Joined Mar 31, 2012
30,060
They way I do it is fairly sophisticated -- and I doubt it would be a good way to code on a resource-starved platform. I've developed a handful of very non-intuitive macros to automatically generate much of my template code for a new object. But, when I get a chance, I can go over it in a bit of detail and perhaps there is something there you can adopt and adapt. But I don't have time today or in the next several days to go into it. Perhaps this weekend.
 

nsaspook

Joined Aug 27, 2009
13,273
The reason I want to use object C like code is I find it a lot more readable, and easier to manager.
YMMV
I've usually found in cases were the embedded code required low-level operations of hardware typically found on 8-bit system it was better to conceptualize (take the time to think like the machine) the operations as a typical machine procedural operations instead of using an object based model. The driver interface abstractions and operations on a 8-bit system are unlikely to contain the complexity required of a highly human interactive element that makes OOP worth the trouble. What really important at this level is structured programming and control constructs, not OOP.
 
Last edited:

Thread Starter

bug13

Joined Feb 13, 2012
2,002
They way I do it is fairly sophisticated -- and I doubt it would be a good way to code on a resource-starved platform. I've developed a handful of very non-intuitive macros to automatically generate much of my template code for a new object. But, when I get a chance, I can go over it in a bit of detail and perhaps there is something there you can adopt and adapt. But I don't have time today or in the next several days to go into it. Perhaps this weekend.
No worries, thanks for considering it!!
 

Thread Starter

bug13

Joined Feb 13, 2012
2,002
YMMV
I've usually found in cases were the embedded code required low-level operations of hardware typically found on 8-bit system it was better to conceptualize (take the time to think like the machine) the operations as a typical machine procedural operations instead of using an object based model. The driver interface abstractions and operations on a 8-bit system are unlikely to contain the complexity required of a highly human interactive element that makes OOP worth the trouble. What really important at this level is structured programming and control constructs, not OOP.
The reason I like to use object like C code is more for the application code. For example, driving a LCD and sending a packet over some kind of communication link.

For example, I may have a driver setup with non-object like C:
Code:
rs485_put(uint8_t one_byte);
uint8_t rs485_get(void);
At my application code, I would like to use object like C:
Code:
com_init(&obj_com, &rs485_put,&rs485_get, other_params);

obj_com.send_handshake();
obj_com.send_ack();
obj_com.send_resend_request();
obj_com.send_raw(uint_t *data);
obj_com.rx_event1_callback(func_ptr);
obj_com.rx_event2_callback(func_ptr);
...
If I want to change to UDP/IP instead of rs485, I just need to write a new driver for UDP/IP:
Code:
udp_put(uint8_t one_byte);
uint8_t udp_get(void);

com_init(&obj_com, &udp_put,&udp_get, other_params);
// then use the obj_com as above
However, I have not tried any of the above code, purely my attempt to show what I think it will work, and maybe (in my opinion) it's a better way to organize code. I am in the process or researching, hence my post asking about Object like C.
 
Last edited:

nsaspook

Joined Aug 27, 2009
13,273
Sure, I've used function pointers for LCD display modes and such. That's a useful abstraction for simplifying the calling code but it doesn't help much with the actual structure of the needed code and data for an embedded application. It's important to modularize code into separate header and code files.

A example:
This applications program fragment changes the operation and display modes from a set of jumpers on test cables for different types of test assemblies using the 'modetype' structure and (*display_func)(void) for a LCD display.

C:
// mandm.h: display and mode defs
typedef void (*display_func)(void);

struct modetype {
    volatile uint8_t demos, move, free, operate, demos_init, slow, emo, v24, cal, on_off_only, idle, locked, info_only, qei, slow_bypass, change;
    display_func display;
};

typedef struct pottype {

    enum movement_t {
        CW, STOP, CCW
    } movement;

    int16_t pos_actual, pos_set, error, pos_actual_prev, pos_change; // in ADC counts
    int16_t limit_change, limit_span, limit_offset, limit_offset_l, limit_offset_h; // AXIS limits for error checking
    int16_t low, high, offset, span, cal_low, cal_high, cal_failed, cal_warn; // end of travel ADC count values
    float scale_out, scale_in; // scaling factor from actual to scaled and back
    int16_t scaled_actual, scaled_set, scaled_error; // 0..1023 value of pot for LCD readback
} volatile pottype;

typedef struct motortype {
    uint8_t type, run, cw, axis, free, slow, active, reversed, v24, slow_only, on_off_only;
    int16_t hunt_count, cal_pos;
    struct pottype pot;
} volatile motortype;

typedef struct knobtype {
    int32_t c, last_c, band; // count
    uint8_t cw, ccw, ticks; // direction

    enum movement_t {
        CW, STOP, CCW
    } movement;
} volatile knobtype;

typedef struct qeitype {
    int32_t c, last_c, band, max; // count
    uint8_t cw, ccw, ticks, home, back_stop, forward_stop; // directions

    enum movement_t {
        CW, STOP, CCW
    } movement;
} volatile qeitype;

//  mandm8722.c: variable mode
volatile struct modetype mode = {FALSE, TRUE, TRUE, HELP_M, TRUE, TRUE, FALSE, FALSE, FALSE, FALSE, TRUE, TRUE, FALSE, FALSE, FALSE};

// config.h data structures and function defs
void init_motordata(BYTE);

extern volatile struct modetype mode;
extern volatile struct motortype motordata[MAX_MOTOR];
extern volatile struct knobtype knob2;
extern volatile struct qeitype qei1;
extern struct V_data V;
extern far int8_t bootstr2[MESG_W + 1];
extern uint32_t rawp[MAX_POT];
extern void term_time(void);
extern void viision_m_display(void);
extern void help_m_display(void);
extern void viision_ms_display(void);
extern void v810_ms_display(void);
extern void varian_v_display(void);
extern void e220_m_display(void);
extern void e220_qei_display(void);
extern void gsd_m_display(void);
extern void default_display(void);
extern void ADC_read(void);

// config.c: init mode variable and other data structues
    if (part == V810_MS || part == EV810_MS) {
        term_time();
        mode.display = v810_ms_display;
        putrs2USART("\x1b[7m Init VISTA MASS SLIT. \x1b[0m\r\n");
        for (z = 0; z < MAX_MOTOR; z++) {
            motordata[z].slow = FALSE;
            motordata[z].slow_only = FALSE;
            motordata[z].active = FALSE;
            motordata[z].reversed = FALSE;
            motordata[z].v24 = FALSE;
            motordata[z].cal_pos = V810_MS_CAL;
            motordata[z].pot.limit_change = V810_MS_CHANGE;
            motordata[z].pot.limit_span = V810_MS_SPAN;
            motordata[z].pot.limit_offset_l = V810_MS_OFFSET_L;
            motordata[z].pot.limit_offset_h = V810_MS_OFFSET_H;
        }
        motordata[0].active = TRUE; // x motor for SLIT position
    }

    if (part == V810_M) {
        term_time();
        mode.display = e220_m_display;
        putrs2USART("\x1b[7m Init VISTA MANIPULATOR.\x1b[0m\r\n");
        for (z = 0; z < MAX_MOTOR; z++) {
            motordata[z].cal_pos = V810_M_CAL;
            motordata[z].pot.limit_change = V810_M_CHANGE;
            motordata[z].pot.limit_span = V810_M_SPAN;
            motordata[z].pot.limit_offset_l = V810_M_OFFSET_L;
            motordata[z].pot.limit_offset_h = V810_M_OFFSET_H;
        }
        //                motordata[2].active = FALSE;
        motordata[0].pot.limit_span = V810_M_SPAN_X;
        motordata[1].pot.limit_span = V810_M_SPAN_Y;
        motordata[2].pot.limit_span = V810_M_SPAN_Z;
    }

//  mandm8722.c: display function code for different assemblies
void v810_ms_display(void)
{
    if (help_pos == 0) {
        sprintf(bootstr2, "                    "); // info display data
        LCD_VC_puts(VC0, DS1, YES);
        sprintf(bootstr2, "%cR%2i Set%2i V%3li I%2li               ", Cursor[0], motordata[0].pot.scaled_actual / 10, motordata[0].pot.scaled_set / 10, R.pos_x, R.current_x);
        LCD_VC_puts(VC0, DS2, YES);
        sprintf(bootstr2, "                    "); // info display data
        LCD_VC_puts(VC0, DS3, YES);
    } else {
        sprintf(bootstr2, "                     ");
        LCD_VC_puts(VC0, DS1, YES);
        sprintf(bootstr2, "                     ");
        if (motordata[0].active) sprintf(bootstr2, "X Pot%3i O%2i S%2i C%2i               ", motordata[0].pot.pos_actual, motordata[0].pot.offset / 10, motordata[0].pot.span / 10, motordata[0].pot.pos_change);
        LCD_VC_puts(VC0, DS2, YES);
        puts2USART(bootstr2);
        putrs2USART("\r\n");
        sprintf(bootstr2, "                     ");
        LCD_VC_puts(VC0, DS3, YES);
    }
}

void e220_m_display(void)
{
    if (help_pos == 0) {
        sprintf(bootstr2, "%cX%3i Set%3i V%3li I%2li               ", Cursor[0], motordata[0].pot.scaled_actual, motordata[0].pot.scaled_set, R.pos_x, R.current_x);
        LCD_VC_puts(VC0, DS1, YES);
        sprintf(bootstr2, "%cY%3i Set%3i V%3li I%2li               ", Cursor[1], motordata[1].pot.scaled_actual, motordata[1].pot.scaled_set, R.pos_y, R.current_y);
        LCD_VC_puts(VC0, DS2, YES);
        sprintf(bootstr2, "%cZ%3i Set%3i V%3li I%2li               ", Cursor[2], motordata[2].pot.scaled_actual, motordata[2].pot.scaled_set, R.pos_z, R.current_z);
        LCD_VC_puts(VC0, DS3, YES);
    } else {
        sprintf(bootstr2, "                     ");
        if (motordata[0].active) sprintf(bootstr2, "X Pot%3i O%2i S%2i C%2i               ", motordata[0].pot.pos_actual, motordata[0].pot.offset / 10, motordata[0].pot.span / 10, motordata[0].pot.pos_change);
        LCD_VC_puts(VC0, DS1, YES);
        puts2USART(bootstr2);
        putrs2USART("\r\n");
        sprintf(bootstr2, "                     ");
        if (motordata[1].active) sprintf(bootstr2, "Y Pot%3i O%2i S%2i C%2i               ", motordata[1].pot.pos_actual, motordata[1].pot.offset / 10, motordata[1].pot.span / 10, motordata[1].pot.pos_change);
        LCD_VC_puts(VC0, DS2, YES);
        puts2USART(bootstr2);
        putrs2USART("\r\n");
        sprintf(bootstr2, "                     ");
        if (motordata[2].active) sprintf(bootstr2, "Z Pot%3i O%2i S%2i C%2i               ", motordata[2].pot.pos_actual, motordata[2].pot.offset / 10, motordata[2].pot.span / 10, motordata[2].pot.pos_change);
        LCD_VC_puts(VC0, DS3, YES);
        puts2USART(bootstr2);
        putrs2USART("\r\n");
    }
}

//  display updates and operational functions
/* assembly selection */
void nav_menu(void) // call the correct screen display function
{
    Set_Cursor();
    mode.display();

}

/* main HID loop */
void hid_menu(void)
{
    static uint8_t menu_pos = HELP_M;

    menu_pos = get_hid();
    check_cable(&menu_pos);
    update_lcd_menu(menu_pos);
    nav_menu();
}

// in main loop
    /* Loop forever */
    while (TRUE) {
        ADC_read();
        if (emotor_is_all_off() && WORKERFLAG) zero_amploc();
        hid_menu();
        ClrWdt(); // reset the WDT timer
        if (mode.cal) {
            run_cal();
            menu_pos = get_hid();
            check_cable(&menu_pos);
            update_lcd_menu(menu_pos);
        }
        ClrWdt(); // reset the WDT timer
        update_hist();
        track_motor();
        if (mode.idle) {
            help_pos = 0;
        }
    }
https://forum.allaboutcircuits.com/threads/mandm.75507/
 
Last edited:

xox

Joined Sep 8, 2017
838
The reason I like to use object like C code is more for the application code. For example, driving a LCD and sending a packet over some kind of communication link.

For example, I may have a driver setup with non-object like C:
Code:
rs485_put(uint8_t one_byte);
uint8_t rs485_get(void);
At my application code, I would like to use object like C:
Code:
com_init(&obj_com, &rs485_put,&rs485_get, other_params);

obj_com.send_handshake();
obj_com.send_ack();
obj_com.send_resend_request();
obj_com.send_raw(uint_t *data);
obj_com.rx_event1_callback(func_ptr);
obj_com.rx_event2_callback(func_ptr);
...
If I want to change to UDP/IP instead of rs485, I just need to write a new driver for UDP/IP:
Code:
udp_put(uint8_t one_byte);
uint8_t udp_get(void);

com_init(&obj_com, &udp_put,&udp_get, other_params);
// then use the obj_com as above
However, I have not tried any of the above code, purely my attempt to show what I think it will work, and maybe (in my opinion) it's a better way to organize code. I am in the process or researching, hence my post asking about Object like C.
You can most certainly do something like that...BUT it will actually make your code less maintainable. The reason being that OOP languages pass an implicit "this" pointer in member function calls whereas C has no such facility and so the price of that sort of syntactic sugar is that you'd need the functions to either (a) store a static pointer to the object or (b) store the pointer elsewhere (meaning some global variable). Neither is a very good idea. And by the way, I say that as a die-hard C++ programmer so it's not like I have some bias against OOP (quite the contrary). But the reality is that you're writing C code. Trying to force it to be some other language just isn't productive.

Code:
typedef struct com_object_t
{
/*
    ...data members...  
*/  
} com_object;

typedef enum com_status_t
{
    COM_OKAY,
    COM_ERROR,  
    COM_READ_ERROR,
    COM_WRITE_ERROR
/*
    ...more status codes...  
*/  
} com_status;

void com_init(com_object* obj)
{
    memset(obj, 0, sizeof(*obj));
/*
    ...whatever...  
*/  
}

com_status com_send_handshake(com_object* obj)
{
/*
    ...do send...  
*/
    return COM_OKAY;
}

/*
    ...more functions here...  
*/

int main(void)
{
    com_object obj;
    com_init(&obj);
    if(com_send_handshake(&obj) != COM_OKAY)
    {  
    /*
        ...report error or what have you...  
    */
    }
/*
    ...etc...  
*/  
}
The OOP equivalent would be many more lines long and much harder to maintain so why even bother?
 

Thread Starter

bug13

Joined Feb 13, 2012
2,002
Sure, I've used function pointers for LCD display modes and such....
Not relative to my OP, but I want to know:
Code:
extern void term_time(void);
extern void viision_m_display(void);
extern void help_m_display(void);
What's the purpose of extern for function? I thought I just need to put the function prototype in a .h file, then the function in a .c file? If I want to use the function, then I will just include that header file??
Code:
// header file
extern volatile uint8_t data;  // global var
void foo(void);                      // function prototype


// c source file
volatile uint8_t data;            // declaration of global var
void foo(void){                     // function
// to stuff
}
 

xox

Joined Sep 8, 2017
838
Not relative to my OP, but I want to know:
Code:
extern void term_time(void);
extern void viision_m_display(void);
extern void help_m_display(void);
What's the purpose of extern for function? I thought I just need to put the function prototype in a .h file, then the function in a .c file? If I want to use the function, then I will just include that header file??
Code:
// header file
extern volatile uint8_t data;  // global var
void foo(void);                      // function prototype


// c source file
volatile uint8_t data;            // declaration of global var
void foo(void){                     // function
// to stuff
}
The "extern" qualifier is purely optional for functions. (Not so for variables of course). You might want to read up on C linkage rules.
 

Thread Starter

bug13

Joined Feb 13, 2012
2,002
You can most certainly do something like that...BUT it will actually make your code less maintainable. The reason being that OOP languages pass an implicit "this" pointer in member function calls whereas C has no such facility and so the price of that sort of syntactic sugar is that you'd need the functions to either (a) store a static pointer to the object or (b) store the pointer elsewhere (meaning some global variable)...
How about something like these from google, not ideal, but it's OK for me:
Code:
// a simple object
typedef struct stest {
    int var1;
    void (*method)(struct stest *self, int num);
}obj_t;

// a method
void func(struct stest *self, int num){
    printf("multiple is: %d\n", num * (*self).var1);
}

// set up objcet
void obj_init(obj_t *obj, int var1){
    (*obj).var1 = var1;
    (*obj).method = &func;
}

int main()
{
    obj_t obj1, obj2;

    obj_init(&obj1, 5);
    obj_init(&obj2, 10);

    obj1.method(&obj1, 2);
    obj2.method(&obj2, 2);
}
Print out:
multiple is: 10
multiple is: 20
What I am going to say might sound like I am changing my goal posts. I don't need to implement a full object in C, I just need to group all the relative functions into one thing (object?? maybe it's wrong to refer it as object because it only have a limited subset of OOP).

I will most likely put one thing (object??) into a .h and .c files, and then all I need to deal with are just 3 things:
Code:
// first, declare object
obj_t obj;
// then initial obj;
obj_init(&obj, var1, var2...);
//  and use the object.
obj.method1(&obj, var1, ...);
obj.method2(&obj, var2, ...);
For me, the above code is a lot clearer than this:
Code:
// declare some global variables so data can be shared over different functions
int var1;
int var2;
...

obj_method1();
obj_method2();
For me, things will get a lot more messier if I have 10 global variables and 10 different different functions.

I often found myself lost if my re-useable code have 10 different functions and a few different global variables. (Especially true when I go back to use this re-useable code a month later)

Hope I am making sense here.
 
Last edited:

xox

Joined Sep 8, 2017
838
I often found myself lost if my re-useable code have 10 different functions and a few different global variables. (Especially true when I go back to use this re-useable code a month later)
Yes definitely avoid global variables if at all possible. Sometimes necessary, but in most cases they just lead to maintainance issues.

And converting your example to standard C:

Code:
// a simple object
typedef struct stest {
    int var1;
}obj_t;
// a method
void method(struct stest *self, int num){
    printf("multiple is: %d\n", num * self->var1);
}
// set up objcet
void obj_init(obj_t *obj, int var1){
   obj->var1 = var1;
}
int main()
{
    obj_t obj1, obj2;
    obj_init(&obj1, 5);
    obj_init(&obj2, 10);
    method(&obj1, 2);
    method(&obj2, 2);
}
As you can see this does exactly the same thing and in less lines of code than the quasi-OOP approach.

And there are certainly situations where storing a function pointer can be useful (like to achieve some sort of polymorphic behaviour) but that too can be easily done using plain C:

Code:
#include <stdlib.h>
#include <stdio.h>
#include <time.h>

struct speaker;
typedef struct speaker speaker;

typedef void (*tongue)(speaker* self);

struct speaker
{
    int number;
    tongue fun;
};

void speaker_init(speaker* self, int number, tongue fun)
{
    self->number = number;
    self->fun = fun;
}

void english(speaker* self)
{
    printf("The number is %d\n", self->number);
}

void spanish(speaker* self)
{
    printf("El numero es %d\n", self->number);
}

void speak(speaker* self)
{
    self->fun(self);
}

int main(void)
{
    srand(time(NULL));
  
    speaker self;
  
    for(;;)
    {
        speaker_init(&self, rand(), (rand() & 1) ? spanish : english);
  
        speak(&self);

        getchar();  
    }
}
 

WBahn

Joined Mar 31, 2012
30,060
You can most certainly do something like that...BUT it will actually make your code less maintainable. The reason being that OOP languages pass an implicit "this" pointer in member function calls whereas C has no such facility and so the price of that sort of syntactic sugar is that you'd need the functions to either (a) store a static pointer to the object or (b) store the pointer elsewhere (meaning some global variable). Neither is a very good idea. And by the way, I say that as a die-hard C++ programmer so it's not like I have some bias against OOP (quite the contrary). But the reality is that you're writing C code. Trying to force it to be some other language just isn't productive.
In my code I get around that by making the 'this' pointer explicit.

I don't try to embed function pointers into every object so that I can use the . (or, in my case, the ->) dereference operators access them. I find that counterproductive as now every object instance has to store a set of function pointers for every method for that object class. OOP languages do NOT do this and I see no reason to burden my objects with this overhead. Instead, I accept that I'm writing object-like code from a conceptual level and deal with syntax issues in a clean and consistent way.

First, I define a structure, such as BALL, in a file called ball.c and I put a typedef for it in ball.h. That means that code that includes ball.h can see the typedef but not the structure itself, thus I can't dink around with the internals of the structure which means that I am completely free to modify the structure definition as I see fit and the only functions that have to be maintained in doing so are also contained in ball.c.

Then, ALL of the functions in ball.c are prefixed with "BALL_". This not only documents in the user code which class a function belongs to, but it also makes if very difficult to get function name conflicts at link time, given C's pretty piss poor notion of namespace. Then all functions that would be considered instance methods in an OOP have, as their first argument, a pointer to an object of that type.

To further leverage the OOP concept of layered data abstraction, the functions defined in ball.c fall into a hierarchy with each data member in the structure having its own primitive (getter/setter) functions. These are the ONLY functions that are allowed to directly access the contents of the structure and these. Using these are low-level utility functions that access the information (not the representation) in the structure via the primitive functions. For instance, if I have a COMPLEX class, I might store the components as real/imaginary or as mag/arg -- that's the data representation. But I have utility functions that return the real component of the imaginary value irrespective of how the information is represented within the structure. Then I have higher level functions that use the utility functions.

The prototypes for only those functions that I want the user to have access to are located in ball.h. This forms the public interface that I have to maintain in a backwards compatible form as I update the class. The reset are private functions used only within the ball.c file and I have a lot more flexibility with those.

I also embed a 32-bit code (just a really poor hash of the class name) in each data structure so that each function can validate that the object passed to it is actually of the type that is expected.

There is a lot of overhead in creating a new class, so I developed some macros that automatically generate the primitive functions as well as the constructor and destructor for the class. I also generate the function names and the self pointer automatically for each function using a macro. I can talk about these more if anyone is interested.
 

xox

Joined Sep 8, 2017
838
In my code I get around that by making the 'this' pointer explicit.

I don't try to embed function pointers into every object so that I can use the . (or, in my case, the ->) dereference operators access them. I find that counterproductive as now every object instance has to store a set of function pointers for every method for that object class. OOP languages do NOT do this and I see no reason to burden my objects with this overhead. Instead, I accept that I'm writing object-like code from a conceptual level and deal with syntax issues in a clean and consistent way.

First, I define a structure, such as BALL, in a file called ball.c and I put a typedef for it in ball.h. That means that code that includes ball.h can see the typedef but not the structure itself, thus I can't dink around with the internals of the structure which means that I am completely free to modify the structure definition as I see fit and the only functions that have to be maintained in doing so are also contained in ball.c.

Then, ALL of the functions in ball.c are prefixed with "BALL_". This not only documents in the user code which class a function belongs to, but it also makes if very difficult to get function name conflicts at link time, given C's pretty piss poor notion of namespace. Then all functions that would be considered instance methods in an OOP have, as their first argument, a pointer to an object of that type.

To further leverage the OOP concept of layered data abstraction, the functions defined in ball.c fall into a hierarchy with each data member in the structure having its own primitive (getter/setter) functions. These are the ONLY functions that are allowed to directly access the contents of the structure and these. Using these are low-level utility functions that access the information (not the representation) in the structure via the primitive functions. For instance, if I have a COMPLEX class, I might store the components as real/imaginary or as mag/arg -- that's the data representation. But I have utility functions that return the real component of the imaginary value irrespective of how the information is represented within the structure. Then I have higher level functions that use the utility functions.

The prototypes for only those functions that I want the user to have access to are located in ball.h. This forms the public interface that I have to maintain in a backwards compatible form as I update the class. The reset are private functions used only within the ball.c file and I have a lot more flexibility with those.

I also embed a 32-bit code (just a really poor hash of the class name) in each data structure so that each function can validate that the object passed to it is actually of the type that is expected.

There is a lot of overhead in creating a new class, so I developed some macros that automatically generate the primitive functions as well as the constructor and destructor for the class. I also generate the function names and the self pointer automatically for each function using a macro. I can talk about these more if anyone is interested.
That's a nice approach. In C++ it's referred to as the PIMPL idiom. Long while back I used that technique quite a bit, though in recent years I've taken up a bit more lax approach. But yes it is a very powerful paradigm, much easier to maintain code at least. But like you say it does tend to require a lot of boilerplate. (And discipline!)

C was actually my first love. And as big of a pain as it can be sometimes, I still enjoy the challenge of putting together a well-oiled C program. C++ on the other hand can almost seem distracting sometimes the way it kind of naturally compels one to always think in terms of "everything as an object". Of course facilities like deterministic destructors, templates, and exceptions are almost too painful to live without, and at any rate C++ is pretty much just a superset of C, so unless it just isn't an option for a project I go with the former. I really try to strike a balance between functional and object-oriented programming anyway so it's not like a big existential issue for me anymore. (Hell my biggest problem these day is mixing up programming language constructs. Like whoops, this is a Python project, can't use Javascript here. :D)
 

nsaspook

Joined Aug 27, 2009
13,273
Not relative to my OP, but I want to know:
Code:
extern void term_time(void);
extern void viision_m_display(void);
extern void help_m_display(void);
What's the purpose of extern for function? I thought I just need to put the function prototype in a .h file, then the function in a .c file? If I want to use the function, then I will just include that header file??
Code:
// header file
extern volatile uint8_t data;  // global var
void foo(void);                      // function prototype
Good catch.
You can use it to share functions that you do NOT want exposed in a public header but in this case it's redundant, not needed now after what looks like a code refactor of extern functions from a .c file to shared .h headers.
 

Thread Starter

bug13

Joined Feb 13, 2012
2,002
In my code I get around that by making the 'this' pointer explicit.

I don't try to embed function pointers into every object so that I can use the . (or, in my case, the ->) dereference operators access them. I find that counterproductive as now every object instance has to store a set of function pointers for every method for that object class. OOP languages do NOT do this and I see no reason to burden my objects with this overhead. Instead, I accept that I'm writing object-like code from a conceptual level and deal with syntax issues in a clean and consistent way.

First, I define a structure, such as BALL, in a file called ball.c and I put a typedef for it in ball.h. That means that code that includes ball.h can see the typedef but not the structure itself, thus I can't dink around with the internals of the structure which means that I am completely free to modify the structure definition as I see fit and the only functions that have to be maintained in doing so are also contained in ball.c.

Then, ALL of the functions in ball.c are prefixed with "BALL_". This not only documents in the user code which class a function belongs to, but it also makes if very difficult to get function name conflicts at link time, given C's pretty piss poor notion of namespace. Then all functions that would be considered instance methods in an OOP have, as their first argument, a pointer to an object of that type.

To further leverage the OOP concept of layered data abstraction, the functions defined in ball.c fall into a hierarchy with each data member in the structure having its own primitive (getter/setter) functions. These are the ONLY functions that are allowed to directly access the contents of the structure and these. Using these are low-level utility functions that access the information (not the representation) in the structure via the primitive functions. For instance, if I have a COMPLEX class, I might store the components as real/imaginary or as mag/arg -- that's the data representation. But I have utility functions that return the real component of the imaginary value irrespective of how the information is represented within the structure. Then I have higher level functions that use the utility functions.

The prototypes for only those functions that I want the user to have access to are located in ball.h. This forms the public interface that I have to maintain in a backwards compatible form as I update the class. The reset are private functions used only within the ball.c file and I have a lot more flexibility with those.

I also embed a 32-bit code (just a really poor hash of the class name) in each data structure so that each function can validate that the object passed to it is actually of the type that is expected.

There is a lot of overhead in creating a new class, so I developed some macros that automatically generate the primitive functions as well as the constructor and destructor for the class. I also generate the function names and the self pointer automatically for each function using a macro. I can talk about these more if anyone is interested.
I can only understand to where you mentioned placing the public stuff in .h file, and private stuff in .c file. Maybe a minimal example code would help, or point me to some terms I can google and read more about it, when you are free of course :)
 
Top