Подтвердить что ты не робот

Один тип с 2 определениями поведения undefined в C?

Рассмотрим библиотеку, в которой у вас есть код. Например, допустим некоторые манипуляции с точками X и Y.

И затем вы создаете свою библиотеку, где вы не хотите разрешать пользователям доступ к вашей переменной struct, поэтому я использую этот подход до сих пор, и он работает нормально, без каких-либо действий undefined:

lib.h:

#ifndef __LIB_H
#define __LIB_H

#ifdef __LIB_INTERNAL
//Structure for single point
typedef struct {
    int x, y;
} Point;

//Casted pointer
#define _P(in)      ((Point *)(in))
#endif

//Define pointer for public use as void pointer
typedef void* Point_p;

//Create point
Point_p createPoint(int x, int y);

#endif

lib.c:

//Define LIB_INTERNAL to allow visible access
#define __LIB_INTERNAL
#include "lib.h"
#include "stdlib.h"

Point_p createPoint(int x, int y) {
    Point_p p = malloc(sizeof(Point));
    _P(p)->x = x; //_P is visible in this function
    _P(p)->y = y;
    return p;
}

main.c:

#include "lib.h"

int main() {
    Point_p p = createPoint(1, 2); //OK
    Point *ptr = createPoint(1, 2); //Error as Point is not visible public

    p->x = 4; //Error as Point_p is void *
}

Таким образом, я уверен, что у пользователя нет прямого доступа к переменной Point, и он вынужден использовать функции для выполнения операций в этой точке.


Теперь я думаю о другом подходе. Но сначала sizeof(void *) и sizeof(Point *) всегда одно и то же, поэтому я хотел бы использовать этот подход, показывая Point_p до lib.c как typedef Point* Point_p и ко всем другим файлам, которые не являются частью библиотеки как typedef void* Point_p.

lib.h

#ifndef __LIB_H
#define __LIB_H

#ifdef __LIB_INTERNAL
//Structure for single point
typedef struct {
    int x, y;
} Point;

//Define pointer for private use as Point pointer
typedef Point* Point_p;

#else

//Define pointer for public use as void pointer
typedef void* Point_p;

#endif


//Create point
Point_p createPoint(int x, int y);

#endif

lib.c:

//Define LIB_INTERNAL to allow visible access
#define __LIB_INTERNAL
#include "lib.h"
#include "stdlib.h"

Point_p createPoint(int x, int y) {
    Point_p p = malloc(sizeof(Point));
    p->x = x; //_P is not needed because Point_p is visible as Point *
    p->y = y;
    return p;
}

main.c: то же, что и предыдущий


Вопрос

Это поведение undefined? Поскольку во втором подходе lib.c видит Point_p как Point *, но main.c все еще видит его как void *, и поэтому lib.c имеет доступ к элементам напрямую без каста до и main.c не имеет его ни может забрасываться, потому что структура Point скрыта.

4b9b3361

Ответ 1

Да, это так. У указателей на конструкцию не гарантируется то же представление, что и указатели void.

Тем не менее, все указатели на структуру гарантируют, что они имеют одинаковое представление независимо от тега, поэтому общий, четко определенный способ решения этого вопроса заключается в предоставлении только прямого объявления структуры в публичном заголовке, а затем использования указателей для этого.

public_header.h

struct Point; //the private header provides the full definition
struct Point* createPoint(int x, int y);
//...

private_header:

#include "public_header.h"
struct Point { int x, y; }; //full definition

Этот подход также не страдает от размытости типа указателей void.

(Вы также должны избегать использования идентификаторов, начинающихся с двух символов подчеркивания или подчеркивания, и буквы верхнего регистра - это поведение undefined).

Ответ 2

Я использую этот подход до сих пор, и он работает нормально, без какого-либо поведения undefined

Я предполагаю, что вы имеете в виду, что представленный вами код демонстрирует наблюдаемое поведение, которое вы ожидаете, в обстоятельствах, в которых вы его протестировали, что совсем не так, как без поведения undefined. Разумеется, код, который вы изначально опубликовали, имел поведение undefined в результате применения оператора sizeof к выражению типа void.

Но сначала sizeof (void *) и sizeof (Point *) всегда одинаковы

C не гарантирует, что представления этих типов указателей эквивалентны. Вы можете, однако, безопасно преобразовать Point * в void * и обратно, где "безопасно" означает, что результат будет сравниваться с исходным Point *.

Я хотел бы использовать этот подход, показывая Point_p в lib.c как typedef Point* Point_p и ко всем другим файлам, которые не являются частью библиотеки как typedef void* Point_p.

Это небезопасно и формально демонстрирует поведение undefined, которое может или не проявляться так, как вы заметили. Даже если вы можете конвертировать между ними, Point * и void * не являются "совместимыми" типами в стандартном смысле этого слова.

Лучшим шаблоном для реализации непрозрачных типов в C является использование неполных типов. Это будет выглядеть примерно так:

lib.h:

// User header for lib
#ifndef __LIB_H
#define __LIB_H

// Structure for a single point -- NO BODY DECLARED
typedef struct point Point;

// Create point
Point *createPoint(int x, int y);

#endif

lib.c:

#include <stdlib.h>
#include "lib.h"

// complete the definition of struct point
struct point {
    int x, y;
};

Point *createPoint(int x, int y) {
    Point *p = malloc(sizeof(*p));
    p->x = x;
    p->y = y;
    return p;
}

При этом у вас нет грязных макросов, контролирующих, какие части заголовка следует использовать, и вам даже не нужно беспокоиться о том, что код клиента просто объявляет __LIB_INTERNAL, чтобы получить доступ к членам структуры, потому что они вообще не находятся в заголовке. Все это, тем не менее, имеет совершенно четкое поведение и, более того, лучше безопаснее, чем использование void * для всего.