使用STDDEF.H的offsetof而非滚动自己的可移植性 [英] Portability of using stddef.h's offsetof rather than rolling your own

查看:192
本文介绍了使用STDDEF.H的offsetof而非滚动自己的可移植性的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这是由三部分组成一个挑剔的细节问题。上下文是希望说服一些人认为它是安全的使用< STDDEF.H>的 的定义offsetof 无条件的,而不是(在某些情况下)滚动自己。有问题的程序在普通的旧的C完全写的,所以请的忽略C ++完全回答时。

第1部分:当以同样的方式为标准 offsetof 使用,不扩大每C89,为什么或者为什么不是这个宏招来未定义的行为,是不是在C99有什么不同?

 的#define offset_of(TP,成员)(((字符*)及((TP *)0) - >成员) - (的char *)0)

请注意:感兴趣的节目,这是取代的指针只能从对方却点到同一阵列中扣除的标准规则的人都实现,通过定义所有的指针,无论类型还是值的,以点成一个单一的全局地址空间。因此,请不要认为这种宏观的扩张引发未定义行为时,依靠这条规则。

第2部分:据您所知,有以前有过一个释放,生产C实现的,吃饱上述宏扩展时,将(在某些情况下)的行为方式不同,将有如果 offsetof 宏已被代替?

第3部分:据您所知,什么是最最近发布的生产C实现,要么没有提供 STDDEF.H 或没有提供一个工作定义的 offsetof 在头?这样做,实施声称是符合C标准的任何版本?

有关部分2和3,请只能回答,如果你可以命名的具体实施并给它发行的日期。该声明可能有资格实现的一般特性的答案是不是对我有用的。


解决方案

要回答2:是的,GCC-4 *(目前我看v4.3.4,发布了2009年8月4日,但它应该保持真所有GCC-4的版本为准)。下面的定义是在他们的STDDEF.H使用:

 的#define offsetof(TYPE,MEMBER)__builtin_offsetof(TYPE,MEMBER)

其中, __ builtin_offsetof 是内建像的sizeof 编译器(也就是它的的的作为宏或运行时功能实施)。编译code:

 的#include<&STDDEF.H GT;结构测试用例{
    字符数组[256];
};诠释主要(无效){
    字符缓冲区[offsetof(结构的测试用例,数组[0]);
    返回0;
}

将导致错误使用您提供的宏扩展(数组大小'缓冲区'不是整型常量-EX pression),但使用STDDEF.H提供的宏时会工作。构建使用gcc-3采用了宏,类似于你的。我想,海湾合作委员会开发商不得不对未定义行为等已EX $ P $这里pssed许多相同的关切,创建内置一个更安全的替代编译器试图生成C code中的等价操作。

其他信息:


  • A <一个href=\"http://ldn.linuxfoundation.org/forum/linux-development-forum/topic/offsetof-changed-gcc-3-gcc-4\">mailing从Linux内核开发人员的名单列表线程

  • GCC的文档offsetof

  • 相关排序的-A <一个href=\"http://stackoverflow.com/questions/400116/what-is-the-purpose-and-return-type-of-the-builtin-offsetof-operator\">question本网站

关于你提到的其他问题:我想的r的答案,他随后的评论做远概述标准的相关部分作为问题#好工作1关注。关于你的第三个问题,我没有听说过的的现代的C编译器不具有 STDDEF.H 。我当然不会考虑缺乏这种基本的标准头为生产的编译器。同样的,如果他们的 offsetof 实施没有工作,那么编译器仍然有工作要做可以认为它是生产之前,就像如果STDDEF.H其他的东西(如 NULL )没有工作。之前到C的标准化公布的C编译器可能不会有这些东西,但ANSI C标准的超过20岁,所以这是非常不可能的,你会遇到其中之一。

整个premise这个问题引出了一个问题:如果这些人相信,他们不能相信的版本offsetof 编译器提供,那么什么样的可以的他们的信任?难道他们相信, NULL 正确定义?难道他们相信,长整型只不过是一个普通小 INT ?难道他们相信,的memcpy 就像它应该?难道他们推出自己的C标准库功能,其余的版本?其中一个很大的原因有语言的标准是这样,你可以信任编译器能够正确地做这些事情。看来愚蠢的信任,除了一切编译offsetof

更新:(响应您的评论)

我想我的同事的行为像您这样做:-)我们的一些较旧的code仍然具有定义自定义宏 NULL VOID ,以及其他类似的东西,因为不同的编译器可以不同的方式实现他们(叹气)。一些这方面code被写回之前C组规范,许多老开发人员仍然在这种心态,即使C标准明确,否则说。

下面一件事可以做,既证明他们是错的,让所有人都满意的同时:

 的#include&LT;&STDDEF.H GT;的#ifndef offsetof
  的#define offsetof(TP,成员)(((字符*)及((TP *)0) - &GT;成员) - (的char *)0)
#万一

在现实中,他们会使用 STDDEF.H 提供的版本。自定义版本总是存在的,但是,如果你碰到一个假想的编译器,没有定义它。

在此基础上,我在过去几年类似的谈话

,我想相信 offsetof 不是标准C的一部分来自于两个地方。首先,它是一个很少使用的功能。开发商不看它,很多时候,让他们忘记它的存在。其次, offsetof 是不是在所有的Kernighan和里奇的重要著作C程序设计语言<提到/ A>(即使是最新版)。这本书的第一版是非官方标准之前,C组规范,我经常听到有人误指的是书作为语言的标准。它更容易比官方标准看,所以我不知道如果我责怪他们使得其职权第一点。不管他们相信什么,但是,这个标准是明确表示, offsetof 是ANSI C(见的r中的链接的答案)。

的一部分

下面是看待问题的#1的另一种方式。该 ANSI C标准给出了如下的定义在第4.1.5:


  offsetof(类型,成员指示符)


  
  

这将扩展为包含有size_t类型的整型常量前pression,
  该值以字节的偏移,其中,所述结构构件
  (由成员指定的标志),从一开始的
  结构(按类型指定)。


使用 offsetof 宏不发生未定义行为。事实上,行为是所有的标准实际上定义。它是由编译器作者定义 offsetof 宏使得它的行为遵循的标准。无论是使用宏,编译器内置的,还是其他什么东西,确保其行为与预期要求实施者深入了解编译器的内部运作和如何实现它会间preT的code。编译器可以使用像地道的版本,你提供一个宏实现它,而只是因为他们知道,编译器将如何处理非标准code。

在另一方面,你所提供的宏扩展确实调用未定义的行为。因为你不知道有足够的了解编译器predict将如何处理code,你不能保证具体的实施的offsetof 总会工作。很多人定义他们自己的版本一样,并没有遇到问题,但是,这并不意味着code是正确的。即使这是一个特定的编译器恰好定义方式 offsetof ,写一个code自己调用UB同时使用所提供的 offsetof 宏不会。

滚动你自己的,宏offsetof 不能没有调用未定义行为(ANSI C A.6.2节未定义行为,第27小点)来完成。符合标准使用的 STDDEF.H 版本offsetof 总是会产生在标准中定义的行为(假设编译器)。我建议不要定义一个定制版本,因为它可能会导致便携性问题,但如果其他人不能再说服的#ifndef offsetof 片段上方提供可能是一个可以接受的妥协。

This is a nitpicky-details question with three parts. The context is that I wish to persuade some folks that it is safe to use <stddef.h>'s definition of offsetof unconditionally rather than (under some circumstances) rolling their own. The program in question is written entirely in plain old C, so please ignore C++ entirely when answering.

Part 1: When used in the same manner as the standard offsetof, does the expansion of this macro provoke undefined behavior per C89, why or why not, and is it different in C99?

#define offset_of(tp, member) (((char*) &((tp*)0)->member) - (char*)0)

Note: All implementations of interest to the people whose program this is supersede the standard's rule that pointers may only be subtracted from each other when they point into the same array, by defining all pointers, regardless of type or value, to point into a single global address space. Therefore, please do not rely on that rule when arguing that this macro's expansion provokes undefined behavior.

Part 2: To the best of your knowledge, has there ever been a released, production C implementation that, when fed the expansion of the above macro, would (under some circumstances) behave differently than it would have if its offsetof macro had been used instead?

Part 3: To the best of your knowledge, what is the most recently released production C implementation that either did not provide stddef.h or did not provide a working definition of offsetof in that header? Did that implementation claim conformance with any version of the C standard?

For parts 2 and 3, please answer only if you can name a specific implementation and give the date it was released. Answers that state general characteristics of implementations that may qualify are not useful to me.

解决方案

To answer #2: yes, gcc-4* (I'm currently looking at v4.3.4, released 4 Aug 2009, but it should hold true for all gcc-4 releases to date). The following definition is used in their stddef.h:

#define offsetof(TYPE, MEMBER) __builtin_offsetof (TYPE, MEMBER)

where __builtin_offsetof is a compiler builtin like sizeof (that is, it's not implemented as a macro or run-time function). Compiling the code:

#include <stddef.h>

struct testcase {
    char array[256];
};

int main (void) {
    char buffer[offsetof(struct testcase, array[0])];
    return 0;
}

would result in an error using the expansion of the macro that you provided ("size of array ‘buffer’ is not an integral constant-expression") but would work when using the macro provided in stddef.h. Builds using gcc-3 used a macro similar to yours. I suppose that the gcc developers had many of the same concerns regarding undefined behavior, etc that have been expressed here, and created the compiler builtin as a safer alternative to attempting to generate the equivalent operation in C code.

Additional information:

Regarding your other questions: I think R's answer and his subsequent comments do a good job of outlining the relevant sections of the standard as far as question #1 is concerned. As for your third question, I have not heard of a modern C compiler that does not have stddef.h. I certainly wouldn't consider any compiler lacking such a basic standard header as "production". Likewise, if their offsetof implementation didn't work, then the compiler still has work to do before it could be considered "production", just like if other things in stddef.h (like NULL) didn't work. A C compiler released prior to C's standardization might not have these things, but the ANSI C standard is over 20 years old so it's extremely unlikely that you'll encounter one of these.

The whole premise to this problems begs a question: If these people are convinced that they can't trust the version of offsetof that the compiler provides, then what can they trust? Do they trust that NULL is defined correctly? Do they trust that long int is no smaller than a regular int? Do they trust that memcpy works like it's supposed to? Do they roll their own versions of the rest of the C standard library functionality? One of the big reasons for having language standards is so that you can trust the compiler to do these things correctly. It seems silly to trust the compiler for everything else except offsetof.

Update: (in response to your comments)

I think my co-workers behave like yours do :-) Some of our older code still has custom macros defining NULL, VOID, and other things like that since "different compilers may implement them differently" (sigh). Some of this code was written back before C was standardized, and many older developers are still in that mindset even though the C standard clearly says otherwise.

Here's one thing you can do to both prove them wrong and make everyone happy at the same time:

#include <stddef.h>

#ifndef offsetof
  #define offsetof(tp, member) (((char*) &((tp*)0)->member) - (char*)0)
#endif

In reality, they'll be using the version provided in stddef.h. The custom version will always be there, however, in case you run into a hypothetical compiler that doesn't define it.

Based on similar conversations that I've had over the years, I think the belief that offsetof isn't part of standard C comes from two places. First, it's a rarely used feature. Developers don't see it very often, so they forget that it even exists. Second, offsetof is not mentioned at all in Kernighan and Ritchie's seminal book "The C Programming Language" (even the most recent edition). The first edition of the book was the unofficial standard before C was standardized, and I often hear people mistakenly referring to that book as THE standard for the language. It's much easier to read than the official standard, so I don't know if I blame them for making it their first point of reference. Regardless of what they believe, however, the standard is clear that offsetof is part of ANSI C (see R's answer for a link).


Here's another way of looking at question #1. The ANSI C standard gives the following definition in section 4.1.5:

     offsetof( type,  member-designator)

which expands to an integral constant expression that has type size_t, the value of which is the offset in bytes, to the structure member (designated by member-designator ), from the beginning of its structure (designated by type ).

Using the offsetof macro does not invoke undefined behavior. In fact, the behavior is all that the standard actually defines. It's up to the compiler writer to define the offsetof macro such that its behavior follows the standard. Whether it's implemented using a macro, a compiler builtin, or something else, ensuring that it behaves as expected requires the implementor to deeply understand the inner workings of the compiler and how it will interpret the code. The compiler may implement it using a macro like the idiomatic version you provided, but only because they know how the compiler will handle the non-standard code.

On the other hand, the macro expansion you provided indeed invokes undefined behavior. Since you don't know enough about the compiler to predict how it will process the code, you can't guarantee that particular implementation of offsetof will always work. Many people define their own version like that and don't run into problems, but that doesn't mean that the code is correct. Even if that's the way that a particular compiler happens to define offsetof, writing that code yourself invokes UB while using the provided offsetof macro does not.

Rolling your own macro for offsetof can't be done without invoking undefined behavior (ANSI C section A.6.2 "Undefined behavior", 27th bullet point). Using stddef.h's version of offsetof will always produce the behavior defined in the standard (assuming a standards-compliant compiler). I would advise against defining a custom version since it can cause portability problems, but if others can't be persuaded then the #ifndef offsetof snippet provided above may be an acceptable compromise.

这篇关于使用STDDEF.H的offsetof而非滚动自己的可移植性的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆