[英]Reliably and portably store and retrieve objects of structure type in C
@bdonlan,在Copying structure in C with assignment 而不是 memcpy()中,列出了使用memcpy
復制結構類型對象的幾個原因。 我還有一個原因:我想使用 memory 的同一區域在不同時間存儲和檢索任意對象(可能具有不同的結構類型) (例如預分配堆上的存儲)。
我想知道:
這是一個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
對應可執行文件的Output
abacadabaabacada-baabacadabaabaca
abacadabaabacada-baabacadabaabaca
EQUAL
abacadabaabacada-baabacadabaabaca-dabaabacadabaaba-cadabaabacadabaa
abacadabaabacada-baabacadabaabaca-dabaabacadabaaba-cadabaabacadabaa
EQUAL
但是,如果遵守標准,什么能保證輸出對始終為EQUAL
呢? 我認為以下內容有幫助(N2176 類型 6.2.5-28):
所有指向結構類型的指針應具有相同的表示和 alignment 要求。
編輯:在考慮了答案和評論之后,我認為以下是更好的 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;
... 是不可移植的擴展。 您的目標似乎是將structure *
用作指向結構的通用指針類型,但是當您將void *
用作指向任何類型的通用指針時,您不需要它。 使用void *
,您甚至可以自動獲得指針轉換,而無需顯式強制轉換。 另請注意,當您調用memcpy()
時,您最終還是會轉換為void *
。
我想使用 memory 的相同區域在不同時間存儲和檢索任意對象(可能具有不同的結構類型)(例如預分配堆上的存儲)。
行。 這不是一個特別大的問題。
我想知道:
- 這如何可移植地完成(在標准定義的行為的意義上)和
你的例子很好。 或者,如果您事先知道可能要存儲的所有不同結構類型,則可以使用union
。
- 標准的哪些部分允許我合理地假設它可以便攜地完成。
在您的動態分配/ memcpy()
示例中,有
C17 7.22.3.4/2:“malloc malloc
為一個object分配空間,其大小由size
指定”
C17 6.2.4/2:“一個 object 存在,有一個不變的地址,並在其整個生命周期中保留其最后存儲的值。”
C17 7.22.3/1:“分配的 object 的生命周期從分配一直延伸到解除分配。”
C17 7.24.2.1/3:“memcpy function 將memcpy
指向的 object 中的n
字符復制到s1
指向的s2
中。”
因此,在一個僅表現出已定義行為的程序中, memcpy()
忠實地將所有指定字節從源對象的表示復制到目標對象的表示。 object 保留它們不變,直到並且除非它們被覆蓋或生命周期結束。 這使它們可用於第二個memcpy()
以將它們從那里復制到其他一些 object。兩個memcpy
都不會改變字節序列,並且分配的 object 忠實地將它們保留在兩者之間,所以最后,所有三個對象 - 原始的,分配的和最終的目的地必須包含相同的字節序列,最多為復制的字節數。
如果您正在詢問某種方法來“存儲”結構,然后將相同的結構恢復到相同類型的 object 中,那么僅復制字節就足夠了。 這可以通過memcpy
完成,並且不需要使用由各種數量的unsigned long long
元素定義的結構進行任何拼湊。 1這由 C 2018 6.2.6.1 第 2 至 4 段保證:
2 除位字段外,對象由一個或多個字節的連續序列組成,其數量、順序和編碼是明確指定的或實現定義的。
3 存儲在無符號位字段和
unsigned char
類型對象中的值應使用純二進制表示法表示。4 存儲在任何其他 object 類型的非位域對象中的值由
n
×CHAR_BIT
位組成,其中n
是該類型的 object 的大小,以字節為單位。 該值可以復制到類型為unsigned char [n]
的 object 中(例如,通過memcpy
); 生成的字節集稱為值的 object 表示……
因此,要存儲任何結構或除位字段以外的任何 object,請為其保留足夠的 memory 2並將對象的字節復制到該 memory。要恢復結構,請將字節復制回來。
關於:
我認為以下內容有幫助(N2176 類型 6.2.5-28):
所有指向結構類型的指針應具有相同的表示和 alignment 要求。
那是無關緊要的。 問題的代碼中沒有使用任何指針的表示形式,因此它們的表示形式(哪些字節構成了指針的記錄值)是無關緊要的。
1為什么要使用不同名稱的多個成員? unsigned long
elements in it, all you need is struct { unsigned long long x[ ]; }
要定義一個包含個
unsigned long
元素的結構,您只需要struct { unsigned long long x[ ]; }
]; } . struct { unsigned long long x[ ]; }
。
2對於 object X
,可以使用void * Memory = malloc(sizeof X)
或者,如果您的編譯器支持可變長度 arrays,則使用unsigned char Memory[sizeof X];
, 或者,如果你想把它放在一個結構中, struct { unsigned char x[sizeof X]; } Memory;
struct { unsigned char x[sizeof X]; } Memory;
.
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.