【发布时间】:2020-10-22 03:30:14
【问题描述】:
这更像是一种好奇心。但我想知道,这段代码在裸机环境中实现memcpy() 的合法性如何?
#define MY_MEMCPY(DST, SRC, SIZE) \
{ struct tmp { char mem[SIZE]; }; *((struct tmp *) ((void *) DST)) = *((struct tmp *) ((void *) SRC)); }
然后我们可以使用它来测试它
#include <stdio.h>
#define MY_MEMCPY(DST, SRC, SIZE) \
{ struct tmp { char mem[SIZE]; }; *((struct tmp *) ((void *) DST)) = *((struct tmp *) ((void *) SRC)); }
int main () {
char buffer[100] = "Hello world";
printf("%s\n", buffer);
MY_MEMCPY(buffer, "one", 4)
printf("%s\n", buffer);
MY_MEMCPY(buffer, "two", 4)
printf("%s\n", buffer);
MY_MEMCPY(buffer, "three", 6)
printf("%s\n", buffer);
return 0;
}
打印出来的
Hello world
one
two
three
据我了解,它不会违反严格的别名规则,因为指向 struct 的指针始终等于指向其第一个成员的指针,在这种情况下,第一个成员是 char。见6.7.2.1p15:
一个指向结构对象的指针,经过适当的转换,指向它的初始成员(或者如果该成员是位域,则指向它所在的单元),反之亦然。
它也不会有对齐问题,因为它的_Alignof() 是1。
进一步阅读:
编辑#1
仅适用于文字字符串,我们可以创建另一个版本的宏,它不需要任何长度作为参数传递。当然目的地仍然需要有足够的内存来保存新的字符串。
这是修改后的版本:
/*
**WARNING** This macro works only when `SRC` is **a literal string** or
in all other cases where its size can be calculated using `sizeof()`
*/
#define MY_LITERAL_MEMCPY(DST, SRC) \
{ struct tmp { char mem[sizeof(SRC)]; }; *((struct tmp *) ((void *) DST)) = *((struct tmp *) ((void *) SRC)); }
我们可以使用它来测试它
#include <stdio.h>
/*
**WARNING** This macro works only when `SRC` is **a literal string** or
in all other cases where its size can be calculated using `sizeof()`
*/
#define MY_LITERAL_MEMCPY(DST, SRC) \
{ struct tmp { char mem[sizeof(SRC)]; }; *((struct tmp *) ((void *) DST)) = *((struct tmp *) ((void *) SRC)); }
int main () {
char buffer[100] = "Hello world";
printf("%s\n", buffer);
MY_LITERAL_MEMCPY(buffer, "one")
printf("%s\n", buffer);
MY_LITERAL_MEMCPY(buffer, "two")
printf("%s\n", buffer);
MY_LITERAL_MEMCPY(buffer, "three")
printf("%s\n", buffer);
return 0;
}
编辑#2
如果您担心假设的外星编译器添加任何可能的填充,添加 _Static_assert() 将使宏非常安全:
MY_MEMCPY():
#define MY_MEMCPY(DST, SRC, SIZE) \
{ struct tmp { char mem[SIZE]; }; _Static_assert(sizeof(struct tmp) \
== SIZE, "You have a very stupid compiler"); \
*((struct tmp *) ((void *) DST)) = *((struct tmp *) ((void *) SRC)); }
MY_LITERAL_MEMCPY():
/*
**WARNING** This macro works only when `SRC` is **a literal string** or
in all other cases where its size can be calculated using `sizeof()`
*/
#define MY_LITERAL_MEMCPY(DST, SRC) \
{ struct tmp { char mem[sizeof(SRC)]; }; _Static_assert(sizeof(struct tmp) \
== sizeof(SRC), "You have a very stupid compiler"); \
*((struct tmp *) ((void *) DST)) = *((struct tmp *) ((void *) SRC)); }
编辑#3
关于代码合法性的讨论
如果将任何内存位置强制转换为char * 是合法的,那么我们可以将非char 类型的每个单个字节映射到不同的char * 变量:
some_non_char_type test;
char * one = (char *) &test;
char * two = (char *) &test + 1;
char * three = (char *) &test + 2;
...
char * last = (char *) &test + sizeof(test) - 1;
如果上面的代码是合法的,那么将上面的所有字节一起映射到一个char数组也是合法的,因为我们正在映射相邻的字节:
char (* all_of_them)[sizeof(some_non_char_type)] = (char (*)[sizeof(some_non_char_type)]) &test;
在这种情况下,我们将以(*all_of_them)[0]、(*all_of_them)[1]、(*all_of_them)[2] 等方式访问它们。
如果将相邻字节的集合映射到char数组是合法的,那么将这样的数组转换为单成员聚合类型是合法的,前提是编译器不为后者添加填充:
struct tmp {
char mem[sizeof(some_non_char_type)];
};
_Static_assert(sizeof(struct tmp) == sizeof(some_non_char_type),
"You have a very stupid compiler");
struct tmp * wrap = (struct tmp *) &test;
编辑#4
这是对 Nate Eldredge 的answer 的回复——似乎启用优化后,编译器可能会做出错误的假设。在将数据复制到DST 之前,通过添加一个简单的*((char *) DST) = 0 来明确告诉编译器我们的别名就足够了。这里是新版本的宏,也可以在启用优化的情况下工作:
MY_MEMCPY():
#define MY_MEMCPY(DST, SRC, SIZE) \
{ struct tmp { char mem[SIZE]; }; _Static_assert(sizeof(struct tmp) \
== SIZE, "You have a very stupid compiler"); *((char *) DST) = 0; \
*((struct tmp *) ((void *) DST)) = *((struct tmp *) ((void *) SRC)); }
MY_LITERAL_MEMCPY()::
/*
**WARNING** This macro works only when `SRC` is **a literal string** or
in all other cases where its size can be calculated using `sizeof()`
*/
#define MY_LITERAL_MEMCPY(DST, SRC) \
{ struct tmp { char mem[sizeof(SRC)]; }; _Static_assert(sizeof(struct tmp) \
== sizeof(SRC), "You have a very stupid compiler"); *((char *) DST) = 0; \
*((struct tmp *) ((void *) DST)) = *((struct tmp *) ((void *) SRC)); }
【问题讨论】:
-
评论不用于扩展讨论;这个对话是moved to chat。
标签: c memcpy bare-metal