在 C 中可靠且可移植地存储和检索结构类型的对象
Reliably and portably store and retrieve objects of structure type in C
@bdonlan,在Copying structure in C with assignment instead of memcpy()中,列出了使用memcpy
复制结构类型对象的几个原因。我还有一个原因:我想使用相同内存区域来存储和检索任意对象——可能不同结构类型——在不同的时间(比如存储在预分配的堆上)。
我想知道:
- 如何做到这一点可移植(在标准定义的行为的意义上)和
- 标准的哪些部分允许我合理地假设它可以移植。
这是一个 MRE(排序:“M”[最小] 没有那么多,我基本上是在询问“R”[可重现]):
编辑:我希望在此之后放置一个更好的例子。我将此留在这里,以便为迄今为止的答案和评论提供参考。
// FILE: memcpy_struct.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// EDIT: @john-bollinger POINTS OUT THAT THE FOLLOWING LINE
// IS NOT PORTABLE.
// typedef struct { } structure ;
// INSTEAD:
typedef struct { char dummy ; } structure ;
typedef struct {
unsigned long long u ; unsigned long long v ;
} unsignedLongLong2; // TWICE AS MANY BITS AS long long
typedef struct
{
unsigned long long u ; unsigned long long v ;
unsigned long long w ; unsigned long long x ;
} unsignedLongLong4; // FOUR TIMES AS MANY BITS AS long long
typedef unsigned char byte ;
void store ( byte * target , const structure * source , size_t size ) {
memcpy ( target , source , size ) ;
}
void fetch ( structure * target , const byte * source , size_t size ) {
memcpy ( target , source , size ) ;
}
const size_t enough =
sizeof ( unsignedLongLong2 ) < sizeof ( unsignedLongLong4 )
? sizeof ( unsignedLongLong4 ) : sizeof ( unsignedLongLong2 ) ;
int main ( void )
{
byte * memory = malloc ( enough ) ;
unsignedLongLong2 v0 = { 0xabacadabaabacada , 0xbaabacadabaabaca } ;
unsignedLongLong4 w0= {
0xabacadabaabacada , 0xbaabacadabaabaca ,
0xdabaabacadabaaba , 0xcadabaabacadabaa } ;
unsignedLongLong2 v1 ;
unsignedLongLong4 w1 ;
store ( memory , ( structure * ) & v0 , sizeof v0 ) ;
fetch ( ( structure * ) & v1 , memory , sizeof v1 ) ;
store ( memory , ( structure * ) & w0 , sizeof w0 ) ;
fetch ( ( structure * ) & w1 , memory , sizeof w1 ) ;
char s [ 1 + sizeof w0 * CHAR_BIT ] ; // ENOUGH FOR TERMINATING NULL CHAR-
char t [ 1 + sizeof w0 * CHAR_BIT ] ; // ACTERS + BASE-2 REPRESENTATION.
sprintf ( s, "%llx-%llx", v0 . u, v0 . v ) ;
sprintf ( t, "%llx-%llx", v1 . u, v1 . v ) ;
puts ( s ) ; puts ( t ) ;
puts ( strcmp ( s , t ) ? "UNEQUAL" : "EQUAL" ) ;
sprintf ( s, "%llx-%llx-%llx-%llx", w0 . u, w0 . v, w0 . w, w0 . x ) ;
sprintf ( t, "%llx-%llx-%llx-%llx", w1 . u, w1 . v, w1 . w, w1 . x ) ;
puts ( s ) ; puts ( t ) ;
puts ( strcmp ( s , t ) ? "UNEQUAL" : "EQUAL" ) ;
free ( memory ) ;
}
编译为
gcc -std=c11 memcpy_struct.c # can do C99 or C17, too
相应可执行文件的输出
abacadabaabacada-baabacadabaabaca
abacadabaabacada-baabacadabaabaca
EQUAL
abacadabaabacada-baabacadabaabaca-dabaabacadabaaba-cadabaabacadabaa
abacadabaabacada-baabacadabaabaca-dabaabacadabaaba-cadabaabacadabaa
EQUAL
但是,如果遵守标准,是什么保证输出对 始终 为 EQUAL
? 我认为以下内容有帮助(N2176 类型 6.2.5-28):
All pointers to structure types shall have
the same representation and alignment requirements as each other.
编辑:在考虑了答案和评论后,我认为以下是更好的MRE:
// FILE: memcpy_struct-1.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
size_t length ;
} array_header ;
typedef struct
{
size_t capacity ;
size_t length ;
} buffer_header ;
const size_t hsize_max =
sizeof ( array_header ) < sizeof ( buffer_header )
? sizeof ( buffer_header ) : sizeof ( array_header ) ;
const size_t block = 512u ;
const size_t pageSize = block * ( 1 +
( hsize_max / block + ! ! hsize_max % block ) ) ;
int main ( void )
{
void * memory = malloc ( pageSize ) ;
array_header a0 = { 42u } ;
buffer_header b0 = { 42u , 0u } ;
array_header a1 ;
buffer_header b1 ;
memcpy ( memory , & a0 , sizeof a0 ) ;
memcpy ( & a1 , memory , sizeof a1 ) ;
memcpy ( memory , & b0 , sizeof b0 ) ;
memcpy ( & b1 , memory , sizeof b1 ) ;
fputs ( "array_header-s are " , stdout ) ;
puts ( a0.length == a1.length ? "EQUAL" : "UNEQUAL" ) ;
fputs ( "buffer_header-s are " , stdout ) ;
puts ( b0.capacity == b1.capacity && b0.length == b1.length
? "EQUAL" : "UNEQUAL" ) ;
free ( memory ) ;
}
既然你问的是可移植性和标准的规定,首先想到的是没有任何成员的结构类型,比如这个...
typedef struct { } structure ;
... 是 non-portable 扩展。您的 objective 似乎可以使用 structure *
作为通用 pointer-to-structure 类型,但是当您有 void *
作为通用 pointer-to-任何类型。使用 void *
,您甚至可以自动获得指针转换,而无需显式强制转换。另请注意,当您调用 memcpy()
.
时,您最终还是会转换为 void *
I want to use the same area of memory to store and retrieve arbitrary objects—of possibly different structure type—at different
times (like storage on a pre-allocated heap).
好的。这不是一个特别大的问题。
I want to know:
- how this can be done portably (in the sense that the behavior defined by the Standard) and
你的例子很好。或者,如果您事先知道可能要存储的所有不同结构类型,则可以使用 union
.
- what parts of the Standard allow me to reasonably assume that it can be done portably.
以你的动态分配/memcpy()
为例,有
C17 7.22.3.4/2:“malloc
函数为大小由 [ 指定的对象分配 space =19=]"
C17 6.2.4/2:“对象存在,具有恒定地址,并在其整个生命周期中保留其 last-stored 值。”
C17 7.22.3/1:“已分配对象的生命周期从
分配直到解除分配。"
C17 7.24.2.1/3:“memcpy
函数从 [= 指向的对象复制 n
个字符22=] 进入指向的对象
通过 s1
."
因此,在仅表现出已定义行为的程序中,memcpy()
忠实地将所有指定字节从源对象的表示复制到目标对象的表示。该对象保持它们不变,直到并且除非它们被覆盖或生命周期结束。这使它们可供第二个 memcpy()
将它们从那里复制到其他对象。 memcpy
都不会改变字节序列,分配的对象忠实地保持它们之间,所以最后,所有三个对象——原始的、分配的和最终的目的地,必须包含相同的字节序列,向上复制的字节数。
如果您正在询问某种方法来“存储”一个结构,然后将相同的结构恢复到相同类型的对象中,那么仅复制字节就足够了。这可以通过 memcpy
来完成,并且不需要使用由不同数量的 unsigned long long
元素定义的结构进行任何拼凑。1 这是由 C 2018 保证的6.2.6.1 第 2 至 4 段:
2 Except for bit-fields, objects are composed of contiguous sequences of one or more bytes, the number,
order, and encoding of which are either explicitly specified or implementation-defined.
3 Values stored in unsigned bit-fields and objects of type unsigned char
shall be represented using a
pure binary notation.
4 Values stored in non-bit-field objects of any other object type consist of n
× CHAR_BIT
bits, where n
is the size of an object of that type, in bytes. The value may be copied into an object of type unsigned char [n]
(e.g., by memcpy
); the resulting set of bytes is called the object representation of the value…
因此,要存储除 bit-field 之外的任何结构或任何对象,请为其预留足够的内存2 并将对象的字节复制到该内存中。要恢复结构,请将字节复制回来。
关于:
I think the following helps (N2176 Types 6.2.5-28):
All pointers to structure types shall have the same representation and alignment requirements as each other.
那是无关紧要的。问题的代码中没有使用任何指针的表示,因此它们的表示(哪些字节构成了指针的记录值)是无关紧要的。
脚注
1 为什么要使用不同名称的多个成员?要定义一个包含 <i>N</i>
unsigned long
元素的结构,您只需要 struct { unsigned long long x [<i>N</i>]; }
.
2 对于一个对象 X
,这可以用 void * Memory = malloc(sizeof X)
或者,如果你的编译器支持可变长度数组,用 unsigned char Memory[sizeof X];
,或者,如果你想把它放在一个结构中,struct { unsigned char x[sizeof X]; } Memory;
.
@bdonlan,在Copying structure in C with assignment instead of memcpy()中,列出了使用memcpy
复制结构类型对象的几个原因。我还有一个原因:我想使用相同内存区域来存储和检索任意对象——可能不同结构类型——在不同的时间(比如存储在预分配的堆上)。
我想知道:
- 如何做到这一点可移植(在标准定义的行为的意义上)和
- 标准的哪些部分允许我合理地假设它可以移植。
这是一个 MRE(排序:“M”[最小] 没有那么多,我基本上是在询问“R”[可重现]):
编辑:我希望在此之后放置一个更好的例子。我将此留在这里,以便为迄今为止的答案和评论提供参考。
// FILE: memcpy_struct.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// EDIT: @john-bollinger POINTS OUT THAT THE FOLLOWING LINE
// IS NOT PORTABLE.
// typedef struct { } structure ;
// INSTEAD:
typedef struct { char dummy ; } structure ;
typedef struct {
unsigned long long u ; unsigned long long v ;
} unsignedLongLong2; // TWICE AS MANY BITS AS long long
typedef struct
{
unsigned long long u ; unsigned long long v ;
unsigned long long w ; unsigned long long x ;
} unsignedLongLong4; // FOUR TIMES AS MANY BITS AS long long
typedef unsigned char byte ;
void store ( byte * target , const structure * source , size_t size ) {
memcpy ( target , source , size ) ;
}
void fetch ( structure * target , const byte * source , size_t size ) {
memcpy ( target , source , size ) ;
}
const size_t enough =
sizeof ( unsignedLongLong2 ) < sizeof ( unsignedLongLong4 )
? sizeof ( unsignedLongLong4 ) : sizeof ( unsignedLongLong2 ) ;
int main ( void )
{
byte * memory = malloc ( enough ) ;
unsignedLongLong2 v0 = { 0xabacadabaabacada , 0xbaabacadabaabaca } ;
unsignedLongLong4 w0= {
0xabacadabaabacada , 0xbaabacadabaabaca ,
0xdabaabacadabaaba , 0xcadabaabacadabaa } ;
unsignedLongLong2 v1 ;
unsignedLongLong4 w1 ;
store ( memory , ( structure * ) & v0 , sizeof v0 ) ;
fetch ( ( structure * ) & v1 , memory , sizeof v1 ) ;
store ( memory , ( structure * ) & w0 , sizeof w0 ) ;
fetch ( ( structure * ) & w1 , memory , sizeof w1 ) ;
char s [ 1 + sizeof w0 * CHAR_BIT ] ; // ENOUGH FOR TERMINATING NULL CHAR-
char t [ 1 + sizeof w0 * CHAR_BIT ] ; // ACTERS + BASE-2 REPRESENTATION.
sprintf ( s, "%llx-%llx", v0 . u, v0 . v ) ;
sprintf ( t, "%llx-%llx", v1 . u, v1 . v ) ;
puts ( s ) ; puts ( t ) ;
puts ( strcmp ( s , t ) ? "UNEQUAL" : "EQUAL" ) ;
sprintf ( s, "%llx-%llx-%llx-%llx", w0 . u, w0 . v, w0 . w, w0 . x ) ;
sprintf ( t, "%llx-%llx-%llx-%llx", w1 . u, w1 . v, w1 . w, w1 . x ) ;
puts ( s ) ; puts ( t ) ;
puts ( strcmp ( s , t ) ? "UNEQUAL" : "EQUAL" ) ;
free ( memory ) ;
}
编译为
gcc -std=c11 memcpy_struct.c # can do C99 or C17, too
相应可执行文件的输出
abacadabaabacada-baabacadabaabaca
abacadabaabacada-baabacadabaabaca
EQUAL
abacadabaabacada-baabacadabaabaca-dabaabacadabaaba-cadabaabacadabaa
abacadabaabacada-baabacadabaabaca-dabaabacadabaaba-cadabaabacadabaa
EQUAL
但是,如果遵守标准,是什么保证输出对 始终 为 EQUAL
? 我认为以下内容有帮助(N2176 类型 6.2.5-28):
All pointers to structure types shall have the same representation and alignment requirements as each other.
编辑:在考虑了答案和评论后,我认为以下是更好的MRE:
// FILE: memcpy_struct-1.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
size_t length ;
} array_header ;
typedef struct
{
size_t capacity ;
size_t length ;
} buffer_header ;
const size_t hsize_max =
sizeof ( array_header ) < sizeof ( buffer_header )
? sizeof ( buffer_header ) : sizeof ( array_header ) ;
const size_t block = 512u ;
const size_t pageSize = block * ( 1 +
( hsize_max / block + ! ! hsize_max % block ) ) ;
int main ( void )
{
void * memory = malloc ( pageSize ) ;
array_header a0 = { 42u } ;
buffer_header b0 = { 42u , 0u } ;
array_header a1 ;
buffer_header b1 ;
memcpy ( memory , & a0 , sizeof a0 ) ;
memcpy ( & a1 , memory , sizeof a1 ) ;
memcpy ( memory , & b0 , sizeof b0 ) ;
memcpy ( & b1 , memory , sizeof b1 ) ;
fputs ( "array_header-s are " , stdout ) ;
puts ( a0.length == a1.length ? "EQUAL" : "UNEQUAL" ) ;
fputs ( "buffer_header-s are " , stdout ) ;
puts ( b0.capacity == b1.capacity && b0.length == b1.length
? "EQUAL" : "UNEQUAL" ) ;
free ( memory ) ;
}
既然你问的是可移植性和标准的规定,首先想到的是没有任何成员的结构类型,比如这个...
typedef struct { } structure ;
... 是 non-portable 扩展。您的 objective 似乎可以使用 structure *
作为通用 pointer-to-structure 类型,但是当您有 void *
作为通用 pointer-to-任何类型。使用 void *
,您甚至可以自动获得指针转换,而无需显式强制转换。另请注意,当您调用 memcpy()
.
void *
I want to use the same area of memory to store and retrieve arbitrary objects—of possibly different structure type—at different times (like storage on a pre-allocated heap).
好的。这不是一个特别大的问题。
I want to know:
- how this can be done portably (in the sense that the behavior defined by the Standard) and
你的例子很好。或者,如果您事先知道可能要存储的所有不同结构类型,则可以使用 union
.
- what parts of the Standard allow me to reasonably assume that it can be done portably.
以你的动态分配/memcpy()
为例,有
C17 7.22.3.4/2:“
malloc
函数为大小由 [ 指定的对象分配 space =19=]"C17 6.2.4/2:“对象存在,具有恒定地址,并在其整个生命周期中保留其 last-stored 值。”
C17 7.22.3/1:“已分配对象的生命周期从 分配直到解除分配。"
C17 7.24.2.1/3:“
memcpy
函数从 [= 指向的对象复制n
个字符22=] 进入指向的对象 通过s1
."
因此,在仅表现出已定义行为的程序中,memcpy()
忠实地将所有指定字节从源对象的表示复制到目标对象的表示。该对象保持它们不变,直到并且除非它们被覆盖或生命周期结束。这使它们可供第二个 memcpy()
将它们从那里复制到其他对象。 memcpy
都不会改变字节序列,分配的对象忠实地保持它们之间,所以最后,所有三个对象——原始的、分配的和最终的目的地,必须包含相同的字节序列,向上复制的字节数。
如果您正在询问某种方法来“存储”一个结构,然后将相同的结构恢复到相同类型的对象中,那么仅复制字节就足够了。这可以通过 memcpy
来完成,并且不需要使用由不同数量的 unsigned long long
元素定义的结构进行任何拼凑。1 这是由 C 2018 保证的6.2.6.1 第 2 至 4 段:
2 Except for bit-fields, objects are composed of contiguous sequences of one or more bytes, the number, order, and encoding of which are either explicitly specified or implementation-defined.
3 Values stored in unsigned bit-fields and objects of type
unsigned char
shall be represented using a pure binary notation.4 Values stored in non-bit-field objects of any other object type consist of
n
×CHAR_BIT
bits, wheren
is the size of an object of that type, in bytes. The value may be copied into an object of typeunsigned char [n]
(e.g., bymemcpy
); the resulting set of bytes is called the object representation of the value…
因此,要存储除 bit-field 之外的任何结构或任何对象,请为其预留足够的内存2 并将对象的字节复制到该内存中。要恢复结构,请将字节复制回来。
关于:
I think the following helps (N2176 Types 6.2.5-28):
All pointers to structure types shall have the same representation and alignment requirements as each other.
那是无关紧要的。问题的代码中没有使用任何指针的表示,因此它们的表示(哪些字节构成了指针的记录值)是无关紧要的。
脚注
1 为什么要使用不同名称的多个成员?要定义一个包含 <i>N</i>
unsigned long
元素的结构,您只需要 struct { unsigned long long x [<i>N</i>]; }
.
2 对于一个对象 X
,这可以用 void * Memory = malloc(sizeof X)
或者,如果你的编译器支持可变长度数组,用 unsigned char Memory[sizeof X];
,或者,如果你想把它放在一个结构中,struct { unsigned char x[sizeof X]; } Memory;
.