LINUX.ORG.RU

История изменений

Исправление quasimoto, (текущая версия) :

sizeof(int*) == sizeof(int) и sizeof(double*) == sizeof(int) - кстати, не факт (x86_64/Linux, например).

Попробуй добавить отладочную печать в оба метода do_something, тогда станет видно, что оба раза вызывается только один из них, в зависимости от того какой вариант выбран в typesafe.

Это случай если не одинаковых типов, то типов с одинаковым sizeof и memory layout - тут просто везёт. Возьмём действительно разные типы:

struct A1 {
    Node1* data;
    Node1* data2;
    Node1*(*fn)(Node1*);
    void do_something() { puts("A1 case"); if (data && fn) data = fn(data); if (data2 && fn) data2 = fn(data2); } };

struct A2 {
    Node2* data;
    Node2*(*fn)(Node2*);
    void do_something() { puts("A2 case"); if (data && fn) data = fn(data); }
};

union A1_or_A2 { A1 a1; A2 a2; };

безопасными будут все (типично) безопасные использования объединений:

    printf("%lu, %lu\n", sizeof(A1), sizeof(A2));

    A1 a1 = { new Node1(1, new Node1(11, NULL)), new Node1(3, new Node1(33, NULL)), repeat_head1 };
    A2 a2 = { new Node2(2, new Node2(22, new Node2(222, NULL))), repeat_head_twice2 };

    A1_or_A2 u;

    u.a1 = a1;
    u.a1.do_something(); // A1 case
    print1(u.a1.data);
    print1(u.a1.data2);

    u.a2 = a2;
    u.a2.do_something(); // A2 case
    print2(u.a2.data);

теперь если инициализировать u одним значением (a1 или a2), но вызвать метод по другому полю (u.a2.do_something() или u.a1.do_something(), соответственно), то что произойдет - метод класса A2 (или A1) будет вызван на объекте класса A1 (или A2), то есть он будет искать значения полей по смещениям одного класса в совсем другом классе:

    u.a1 = a1;
    u.a2.do_something(); // A2 case + segfault!
    print1(u.a1.data);
    print1(u.a1.data2);

    u.a2 = a2;
    u.a1.do_something(); // A1 case
    print2(u.a2.data);

в первом случае вполне может быть сегфолт, во втором, опять, просто везёт.

То есть, тот typesafe можно написать двумя вариантами и разницы между ними нет только при A == B в смысле memory layout. Что касается

    u.a1 = a1;
    print2(u.a2.data);

    u.a2 = a2;
    print1(u.a1.data);

то в первом случае мы лезем в объект класса A1 используя смещение из класса A2 и нам везёт (для data), но потом print2 пытается печатать список указателей на int как список указателей на указатели на double, то есть получается лишнее разыменование и всё падает. Во втором случае нам опять везёт с data, а print1 успешно печатает список **double как список *int, то есть разыменовывая как (int)(*(*int)((**double)x)), _если_ sizeof(**double) <= sizeof(*int) <= sizeof(int) (или для малых адресов), то мы увидим не сами doublы а те адреса по которым они лежат.

Исходная версия quasimoto, :

sizeof(int*) == sizeof(int) и sizeof(double*) == sizeof(int) - кстати, не факт (x86_64/Linux, например).

Попробуй добавить отладочную печать в оба метода do_something, тогда станет видно, что оба раза вызывается только один из них, в зависимости от того какой вариант выбран в typesafe.

Это случай если не одинаковых типов, то типов с одинаковым sizeof и memory layout - тут просто везёт. Возьмём действительно разные типы:

struct A1 {
    Node1* data;
    Node1* data2;
    Node1*(*fn)(Node1*);
    void do_something() { puts("A1 case"); if (data && fn) data = fn(data); if (data2 && fn) data2 = fn(data2); } };

struct A2 {
    Node2* data;
    Node2*(*fn)(Node2*);
    void do_something() { puts("A2 case"); if (data && fn) data = fn(data); }
};

union A1_or_A2 { A1 a1; A2 a2; };

безопасными будут все (типично) безопасные использования объединений:

    printf("%lu, %lu\n", sizeof(A1), sizeof(A2));

    A1 a1 = { new Node1(1, new Node1(11, NULL)), new Node1(3, new Node1(33, NULL)), repeat_head1 };
    A2 a2 = { new Node2(2, new Node2(22, new Node2(222, NULL))), repeat_head_twice2 };

    A1_or_A2 u;

    u.a1 = a1;
    u.a1.do_something(); // A1 case
    print1(u.a1.data);
    print1(u.a1.data2);

    u.a2 = a2;
    u.a2.do_something(); // A2 case
    print2(u.a2.data);

теперь если инициализировать u одним значением (a1 или a2), но вызвать метод по другому полю (u.a2.do_something() или u.a1.do_something(), соответственно), то что произойдет - метод класса A2 (или A1) будет вызван на объекте класса A1 (или A2), то есть он будет искать значения полей по смещениям одного класса в совсем другом классе:

    u.a1 = a1;
    u.a2.do_something(); // A2 case + segfault!
    print1(u.a1.data);
    print1(u.a1.data2);

    u.a2 = a2;
    u.a1.do_something(); // A1 case
    print2(u.a2.data);

в первом случае вполне может быть сегфолт, во втором, опять, просто везёт.

То есть, тот typesafe можно написать двумя вариантами и разницы между ними нет только при A == B в смысле memory layout. Что касается

    u.a1 = a1;
    print2(u.a2.data);

    u.a2 = a2;
    print1(u.a1.data);

то в первом случае мы лезем в объект класса A1 используя смещение из класса A2 и нам везёт (для data), но потом print2 пытается печатать список указателей на int как список указателей на указатели на double, то есть получается лишнее разыменование и всё падает. Во втором случае нам опять везёт с data, а print1 успешно печатает список **double как список *int, то есть разыменовывая как (int)(*(*int)((**double)x)), _если_ sizeof(**double) == sizeof(*int) == sizeof(int) (или для малых адресов), то мы увидим не сами doublы а те адреса по которым они лежат.