Polmorphism application problem in C language

2

this is my first question in stackowerflow. So i'm excited :) I am trying to practice polymorphism in C. I have read many articles. I wrote a small sample code with what I understood. my goal is to create one shape class. this class includes field and length methods. The rectangle and circle classes that derive from this class make their own calculation of the field and length methods.

But there is a problem during compilation: enter image description here Exception thrown at 0x000000000000000A in OOP_in_C_example_3_polymorphism_3.exe: 0xC0000005: Access violation executing location 0x000000000000000A.

it gets better when I delete one of the lines. but this time it operates according to the last called constructor: enter image description here

Where am I having problems?

shape.h:

#include <stdint.h>

struct Vtable;

typedef struct{
    struct Vtable* vptr;
    uint16_t x;
    uint16_t y;

}Shape;

struct Vtable {
    void(*area)(Shape* me);
    void(*lenght)(Shape* me);
};

inline void area(Shape* me) {
    (*me->vptr->area)(me);
}

inline void lenght(Shape* me) {
    (*me->vptr->lenght)(me);
}

void shape_ctor(Shape* me, int x, int y);

shape.c:

#include "shape.h"

void shape_ctor(Shape* me,int x, int y) {   
    struct Vtable vtbl = { &area,&lenght };
    me->vptr = &vtbl;
    me->x   = x;
    me->y   = y;
}

rect.h:

#include "shape.h"

typedef struct  {
    Shape super;
    uint16_t en;
    uint16_t boy;
}rect;

void rect_ctor(rect* me, uint16_t x, uint16_t y, uint16_t en, uint16_t boy);

rect.c:

#include "rect.h"
#include <stdio.h>

void rect_lenght(void* me);
void rect_area(void* me);


void rect_ctor(rect* me,uint16_t x, uint16_t y, uint16_t en, uint16_t boy) {
    struct Vtable vtbl = { &rect_area, &rect_lenght };
    
    shape_ctor(&me->super, x, y);
    me->super.vptr = &vtbl;                 //override vptr
    me->en  = en;
    me->boy = boy;
}


void rect_lenght(void* me) {
    rect* me_ = (rect*)me;      //downcast işlemi
    printf("rectangle lenght: %d\n",(me_->en + me_->boy) * 2);
}
void rect_area(void* me) {
    rect* me_ = (rect*)me;      //downcast işlemi
    printf("rectangle area: %d\n", (me_->en)*(me_->boy));
}

circle.h:

#include "shape.h"

typedef struct {
    Shape super;
    uint16_t radius;
}circle;


void circle_ctor(circle* me, uint16_t x, uint16_t y, uint16_t r);

circle.c:

#include "circle.h"
#include <stdio.h>

void circle_lenght(void* me);
void circle_area(void* me );


void circle_ctor(circle* me, uint16_t x, uint16_t y, uint16_t r) {
    struct Vtable vtbl = {&circle_area,&circle_lenght};

    shape_ctor(&me->super, x, y);
    me->super.vptr = &vtbl;
    me->radius = r;
}


void circle_lenght(void* me) {
    circle* me_ = (circle*)me;
    printf("circle lenght: %f\n", (me_->radius) * (2) * (3.14));
}
void circle_area(void* me){
    circle* me_ = (circle*)me;
    printf("circle area: %f\n", (me_->radius) * (me_->radius) * (3.14));
}

main.c:

#include <stdio.h>
#include "rect.h"
#include "circle.h"


int main() {
    rect  k1;
    circle d1;

    rect_ctor(&k1, 10, 10, 15, 20);
    circle_ctor(&d1, 10, 10, 5);
    
    
    k1.super.vptr->lenght(&k1);
    d1.super.vptr->lenght(&d1);
}
c
oop
polymorphism
asked on Stack Overflow Jul 7, 2020 by Cemal • edited Jul 7, 2020 by Cemal

1 Answer

2

But there is a problem during compilation: enter image description here

This was during the execution time, not during the compilation

You have undefined behaviors because you use the address of local variable after you leave the functions, you need to replace

void shape_ctor(Shape* me,int x, int y) {   
    struct Vtable vtbl = { &area,&lenght };

by

void shape_ctor(Shape* me,int x, int y) {   
    static struct Vtable vtbl = { &area,&lenght };

and

void circle_ctor(circle* me, uint16_t x, uint16_t y, uint16_t r) {
    struct Vtable vtbl = {&circle_area,&circle_lenght};

by

void circle_ctor(circle* me, uint16_t x, uint16_t y, uint16_t r) {
    static struct Vtable vtbl = {&circle_area,&circle_lenght};

I had to move the definition of area and length from shape.h to shape.c, and I removed the useless inline for them.

I also had to protect the headers against the multiple inclusions, example :

#ifndef SHAPE_H
#define SHAPE_H

#include <stdint.h>

struct Vtable;

typedef struct{
    struct Vtable* vptr;
    uint16_t x;
    uint16_t y;
}Shape;

struct Vtable {
    void(*area)(Shape* me);
    void(*lenght)(Shape* me);
};

void shape_ctor(Shape* me, int x, int y);

#endif

After that, compilation and execution :

/tmp % gcc -Wall -g *.c
circle.c: In function 'circle_ctor':
circle.c:9:19: warning: initialization from incompatible pointer type [enabled by default]
     static struct Vtable vtbl = {&circle_area,&circle_lenght};
                   ^
circle.c:9:19: warning: (near initialization for 'vtbl.area') [enabled by default]
circle.c:9:19: warning: initialization from incompatible pointer type [enabled by default]
circle.c:9:19: warning: (near initialization for 'vtbl.lenght') [enabled by default]
main.c: In function 'main':
main.c:14:5: warning: passing argument 1 of 'k1.super.vptr->lenght' from incompatible pointer type [enabled by default]
     k1.super.vptr->lenght(&k1);
     ^
main.c:14:5: note: expected 'struct Shape *' but argument is of type 'struct rect *'
main.c:15:5: warning: passing argument 1 of 'd1.super.vptr->lenght' from incompatible pointer type [enabled by default]
     d1.super.vptr->lenght(&d1);
     ^
main.c:15:5: note: expected 'struct Shape *' but argument is of type 'struct circle *'
rect.c: In function 'rect_ctor':
rect.c:9:19: warning: initialization from incompatible pointer type [enabled by default]
     static struct Vtable vtbl = { &rect_area, &rect_lenght };
                   ^
rect.c:9:19: warning: (near initialization for 'vtbl.area') [enabled by default]
rect.c:9:19: warning: initialization from incompatible pointer type [enabled by default]
rect.c:9:19: warning: (near initialization for 'vtbl.lenght') [enabled by default]
/tmp % ./a.out
rectangle lenght: 70
circle lenght: 31.400000
/tmp % 

Modifying main to allocate the elements in the heap to allow valgrind to know they size to detect possible accesses out of them :

#include <stdlib.h>

#include "rect.h"
#include "circle.h"

int main() {
    rect  * k1 = malloc(sizeof(*k1));
    circle * d1 = malloc(sizeof(*d1));

    rect_ctor(k1, 10, 10, 15, 20);
    circle_ctor(d1, 10, 10, 5);
    
    
    k1->super.vptr->lenght(k1);
    d1->super.vptr->lenght(d1);
    
    free(k1);
    free(d1);
    
    return 0;
}

Execution under valgrind :

/tmp % valgrind ./a.out
==53229== Memcheck, a memory error detector
==53229== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==53229== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==53229== Command: ./a.out
==53229== 
rectangle lenght: 70
circle lenght: 31.400000
==53229== 
==53229== HEAP SUMMARY:
==53229==     in use at exit: 0 bytes in 0 blocks
==53229==   total heap usage: 2 allocs, 2 frees, 48 bytes allocated
==53229== 
==53229== All heap blocks were freed -- no leaks are possible
==53229== 
==53229== For counts of detected and suppressed errors, rerun with: -v
==53229== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
/tmp % 

To not have all these warnings when compiling I encourage you to add the needed casts.


Note to define the vtbl is also better, you do not want to have them destructed by error, so

void shape_ctor(Shape* me,int x, int y) {   
    static const struct Vtable vtbl = { &area,&lenght };

void circle_ctor(circle* me, uint16_t x, uint16_t y, uint16_t r) {
    static const struct Vtable vtbl = {&circle_area,&circle_lenght};

and in shape.h :

typedef struct{
    const struct Vtable* vptr;

The more you can declare member/variable/parameter const the better it is including to detect error when compiling.

answered on Stack Overflow Jul 7, 2020 by bruno • edited Jul 7, 2020 by bruno

User contributions licensed under CC BY-SA 3.0