0

所以我第 64 次尝试编写一个缓冲库,我开始研究一些非常高级的东西。以为我会要求对此进行一些专业的投入。

在我的第一个头文件中,我有这个:

typedef struct StdBuffer { void* address; } StdBuffer;
extern void StdBufferClear(StdBuffer);

在第一个头文件的另一个头文件中,#includes我有这个:

typedef struct CharBuffer { char* address; } CharBuffer;
void (*CharBufferClear)(CharBuffer) = (void*) StdBufferClear;

声明这个函数指针 void 会干扰调用吗?它们具有按值签名匹配。我以前从未见过声明为 void 的函数指针,但它是让它干净编译的唯一方法。

Stackwise 它应该与我在汇编程序编码中学到的东西没有任何区别。

无关紧要的天哪!我刚刚在 StackOverflow 上说 Stackwise!

嗯..看起来我在这里假设太多了。如果可以,请允许我再次澄清。我不在乎地址中存储了什么“类型”的数据。我所关心的只是一个“单元”的大小以及地址上有多少个单元。如果您愿意,请查看 API 的接口协议合约:

typedef struct StdBuffer {

    size_t width;        ///< The number of bytes that complete a data unit.
    size_t limit;        ///< The maximum number of data units that can be allocated for this buffer.
    void * address;      ///< The memory address for this buffer.
    size_t index;        ///< The current unit position indicator.
    size_t allocated;    ///< The current number of allocated addressable units.
    StdBufferFlags flags;///< The API contract for this buffer.

} StdBuffer;

你看,memcpy、memmove 之类的并不真正关心地址上的内容,他们想要的只是我清楚地在这里跟踪的细节。

现在看一下遵循此合同的第一个原型:

typedef struct CharBuffer {

    size_t width;        ///< The number of bytes that complete a data unit.
    size_t limit;        ///< The maximum number of data units that can be allocated for this buffer.
    char * address;      ///< The memory address for this buffer.
    size_t index;        ///< The current unit position indicator.
    size_t allocated;    ///< The current number of allocated addressable units.
    CharBufferFlags flags;///< The API contract for this buffer.

} CharBuffer;

正如您清楚地看到的那样,数据类型在这种情况下是无关紧要的。您可以说 C 会根据情况以不同的方式处理它,但归根结底,只要我们在同一台机器上处理内存,anaddress就是 an address, abytebyte和 along是 a 。long

这个系统组合在一起的目的是消除所有这种类型的杂耍 C 似乎很自豪(而且理所当然地......)它对我想做的事情毫无意义。这是为位于任何地址的任何标准大小的数据(1、2、4、8、sizeof(RandomStruct))创建一个遵守合同的原型。

能够使用代码执行我自己的转换,并使用 api 函数操作该数据,这些函数在具有特定长度内存单元的特定长度内存块上运行。但是,原型必须包含官方数据指针类型,因为最终用户每次想要使用该地址指针做某事时都必须重新转换他们的数据是没有意义的。如果指针为空,则称其为 CharBuffer 是没有意义的。

是一种通用类型,除了在 api 本身内之外,StdBuffer永远不会使用它来管理所有遵守合同的数据类型。

该系统将包含的 api 来自我最新版本的缓冲。@Google Code这里非常清楚地记录了这一点,我知道有些事情需要改变才能将这一切整合在一起,即如果没有大量适当的研究和意见收集,我将无法直接从 api 中安全地操作数据。

这让我注意到我还需要 StdBufferFlags 成员中的有符号/无符号位标志。

也许这个谜题的最后一部分也是为了让你细读。

/** \def BIT(I)
    \brief A macro for setting a single constant bit.
 *
 *  This macro sets the bit indicated by I to enabled.
 *  \param I the (1-based) index of the desired bit to set.
 */
 #define BIT(I) (1UL << (I - 1))

/** \enum StdBufferFlags
    \brief Flags that may be applied to all StdBuffer structures.

 *  These flags determine the contract of operations between the caller
 *  and the StdBuffer API for working with data. Bits 1-4 are for the
 *  API control functions. All other bits are undefined/don't care bits.
 *
 *  If your application would like to use the don't care bits, it would
 *  be smart not to use bits 5-8, as these may become used by the API
 *  in future revisions of the software.

*/
typedef enum StdBufferFlags {

    BUFFER_MALLOCD = BIT(1),    ///< The memory address specified by this buffer was allocated by an API
    BUFFER_WRITEABLE = BIT(2),  ///< Permission to modify buffer contents using the API
    BUFFER_READABLE = BIT(3),   ///< Permission to retrieve buffer contents using the API
    BUFFER_MOVABLE = BIT(4)     ///< Permission to resize or otherwise relocate buffer contents using the API

}StdBufferFlags;
4

4 回答 4

3

此代码需要诊断:

void (*CharBufferClear)(CharBuffer) = (void*) StdBufferClear;

您正在将void *指针转换为没有强制转换的函数指针。在 C 中,void *指针可以在没有强制转换的情况下转换为指向对象类型的指针,但不能转换为函数指针类型。(在 C++ 中,为了增加安全性,还需要转换来转换void *为对象类型。)

您在这里想要的只是在函数指针类型之间进行转换,即:

void (*CharBufferClear)(CharBuffer) = (void (*)(CharBuffer)) StdBufferClear;

然后你仍然在做相同类型的双关语,因为函数是不同的类型。您正在尝试调用一个函数,该函数StdBuffer使用一个指向一个函数的指针,该函数使用一个CharBuffer.

这种类型的代码不是定义明确的 C。在打败了类型系统之后,你靠自己,依靠测试、检查目标代码,或者从编译器编写者那里获得一些保证,这种事情适用于那个编译器.

您在汇编程序编码中学到的内容并不适用,因为汇编语言只有少量基本数据类型,例如“机器地址”或“32 位字”。具有相同布局和低级表示的两个数据结构可能是不兼容类型的概念不会出现在汇编语言中。

即使两种类型在低级别看起来相同(另一个示例:unsigned int有时unsigned long完全相同),C 编译器也可以基于没有违反类型规则的假设来优化程序。例如,假设它A指向B同一个内存位置。如果您分配给一个 object A->member,C 编译器可以假定该对象B->member不受此影响,如果A->member并且B->member具有不兼容的类型,例如 one beingchar *和 other void *。生成的代码一直在寄存器中缓存 的旧值B->member,即使内存中的副本被对A->member. 这是一个无效别名的例子。

于 2012-04-24T20:09:17.037 回答
1

该标准没有定义将函数指针转换为void *.

同样,在函数指针之间转换然后通过错误的指针调用也是未定义的行为。

于 2012-04-24T20:11:53.363 回答
0

任何符合标准的 C 编译器都需要一致地实现某些构造,并且 99% 的 C 编译器确实一致地实现了某些构造,但符合标准的编译器可以自由地以不同的方式实现。尝试将指向采用一种类型指针的函数的指针转换为指向采用另一种类型指针的函数的指针属于后一类。尽管 C 标准规定 avoid*和 achar*大小必须相同,没有什么要求它们共享相同的位级存储格式,更不用说参数传递约定了。虽然大多数机器允许以与字几乎相同的方式访问字节,但这种能力并不普遍。application-binary-interface [指定如何将参数传递给例程的文档] 的设计者可能会指定 achar*以最大化字节访问效率的方式传递,而 avoid*应该以一种方式传递最大限度地提高字访问的效率,同时保留保持未对齐字节地址的能力,可能通过使用补充字来保持零或一来指示 LSB/MSB)。在这样的机器上,有一个例程期望void*从期望传递的代码调用char*可能导致例程访问任意错误数据。

于 2013-01-09T16:41:48.713 回答
-1

不,使用什么数据类型来存储数据并不重要。只有 C 用于读取和写入该数据的类型以及数据是否足够大才重要。

于 2012-04-24T20:07:29.587 回答