tags:

views:

195

answers:

3

Hi,I am trying to explore OOP in C. I am however a C n00b and would like to pick the brilliant brains of stackoverflow :)

My code is below:

#include <stdio.h>
#include <stdlib.h>

typedef struct speaker {
    void (*say)(char *msg);
} speaker;

void say(char *dest) {
  printf("%s",dest);
}

speaker* NewSpeaker() {
  speaker *s;
  s->say = say;
  return s;
}

int main() {
  speaker *s = NewSpeaker();
  s->say("works");
}

However I'm getting a segfault from this, if I however remove all args from say and make it void, I can get it to work properly. What is wrong with my current code?

Also. While this implements a form of object in C, I'm trying to further implement it with inheritance, and even overriding/overloading of methods. How do you think I can implement such?

Thank You!

+15  A: 

In your code, NewSpeaker() doesn't actually create a "new" speaker. You need to use a memory allocation function such as malloc or calloc.

speaker* NewSpeaker() {
   speaker *s = malloc(sizeof(speaker));
   s->say = say;
   return s;
}

Without assigning the value from, for example, the return value of malloc, s is initialized to junk on the stack, hence the segfault.

sixlettervariables
Meta-comment: by "initialized to junk on the stack," I mean whatever happens to be sitting there. Nothing actually "sets" `s` to a value in an active sense. `s` passively acquires the contents of whatever happens to be where it is assigned to in memory.
sixlettervariables
Good answer, but it's generally bad practice to cast the value returned from malloc(), at least in C...
Mark Bessey
Good catch. Some of the compilers I'm stuck with require it, removed.
sixlettervariables
@sixlettervariables: Well, this is not exactly correct. There's a difference between a non-volatile variable "initialized with junk" and a non-volatile variable that "is not initialized at all". A variable that is not initialized at all is not even guaranteed to hold a stable value, while a variable that is initialized (even with "junk") is required to hold that value until it is explicitly changed. In this case we have an *unitialized* variable, not a variable initialized with junk.
AndreyT
For the C layperson "initialized with junk" provides enough to figure out why it didn't work as expected.
sixlettervariables
+2  A: 

You can implement inheritance by embedding the parent class structure in the top of the child class structure. That way you can safely cast from the child class to the parent class. Here's an article on implementing OO features in C. If you want an existing solution, or just want to learn more about ways of achieving OO, look at the GObject library.

Matt Olenik
funny, this is actually the best answer... :)
roe
+3  A: 

Firstly, as it has been noted already, you failed to allocate memory for your 'speaker' object in 'NewSpeaker'. Without the unnecessary clutter it would look as follows

speaker* NewSpeaker(void) 
{
  speaker *s = malloc(sizeof *s);   
  s->say = say;   
  return s;
}

Note, that there's no cast on the result of the malloc, no type name in the 'sizeof' argument and the function parameter list is declared as '(void)', not just '()'.

Secondly, if you want to be able to create non-dynamic objects of your 'speaker' type, you might want to provide an in-place initialization function first, and then proceed from there

speaker* InitSpeaker(speaker* s)
{
  assert(s != NULL);
  s->say = say;
  return s;
}

speaker* NewSpeaker(void)
{
  void *raw = malloc(sizeof(speaker));
  return raw != NULL ? InitSpeaker(raw) : NULL;
}

Finally, if you really want to create something like virtual C++ methods, you need to supply each method with a 'this' parameter (to get access to other members of your object). So it should probably look something like

typedef struct speaker 
{    
  void (*say)(struct speaker *this, char *msg);
} speaker;

void say(speaker *this, char *dest) 
{  
  printf("%s",dest);
}

This, of course, will require you to pass the corresponding argument every time you call a "method", but there's no way around this.

Additionally, I hope you know that you need "method" pointers in your "class" for "virtual methods" only. Ordinary (non-virtual) methods don't need such pointers.

Finally, a "traditional" C++ class imlementation doesn't store virtual method pointers inside each instance of the class. Instead, they are placed in a separate table (VMT), pointer to which is added to each instance. This saves a lot of memory. And this, BTW, makes especially good sense when you implement inheritance.

AndreyT