执行memcpy(0,0,0)是否保证安全?

发布于 2024-10-20 18:07:45 字数 147 浏览 1 评论 0原文

我不太熟悉 C 标准,所以请耐心等待。

我想知道标准是否保证 memcpy(0,0,0) 是安全的。

我能找到的唯一限制是,如果内存区域重叠,则行为未定义......

但是我们可以认为内存区域在这里重叠吗?

I am not so well-versed in the C standard, so please bear with me.

I would like to know if it is guaranteed, by the standard, that memcpy(0,0,0) is safe.

The only restriction I could find is that if the memory regions overlap, then the behavior is undefined...

But can we consider that the memory regions overlap here ?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

如梦初醒的夏天 2024-10-27 18:07:45

我有 C 标准 (ISO/IEC 9899:1999) 的草案版本,其中有一些关于该调用的有趣内容。对于初学者来说,它提到 (§7.21.1/2) 关于 memcpy

其中声明为 size_t n 的参数指定数组的长度
函数,n 可以在调用该函数时具有零值。除非明确说明
否则,在本子条款中特定函数的描述中,指针参数
此类调用中的 仍应具有有效值,如 7.1.4 中所述。在这样的通话中,
查找某个字符未出现的函数、比较两个字符的函数
字符序列返回零,复制字符的函数复制零
字符。


这里指出的参考指向这一点:

如果函数的参数具有无效值(例如
在函数域之外,或者在程序地址空间之外的指针,
或空指针,或指向不可修改存储的指针(当相应的
参数不是 const 限定的)或函数不期望的类型(提升后)
使用可变数量的参数,行为未定义

因此,根据 C 规范,调用

memcpy(0, 0, 0)

会导致未定义的行为,因为空指针被视为“无效值”。

也就是说,如果您这样做的话,如果 memcpy 的任何实际实现都崩溃了,我会感到非常惊讶,因为如果您说复制零字节,我能想到的大多数直观实现都不会执行任何操作。

I have a draft version of the C standard (ISO/IEC 9899:1999), and it has some fun things to say about that call. For starters, it mentions (§7.21.1/2) in regards to memcpy that

Where an argument declared as size_t n specifies the length of the array for a
function, n can have the value zero on a call to that function. Unless explicitly stated
otherwise in the description of a particular function in this subclause, pointer arguments
on such a call shall still have valid values, as described in 7.1.4
. On such a call, a
function that locates a character finds no occurrence, a function that compares two
character sequences returns zero, and a function that copies characters copies zero
characters.

The reference indicated here points to this:

If an argument to a function has an invalid value (such as a value
outside the domain of the function, or a pointer outside the address space of the program,
or a null pointer, or a pointer to non-modifiable storage when the corresponding
parameter is not const-qualified) or a type (after promotion) not expected by a function
with variable number of arguments, the behavior is undefined.

So it looks like according to the C spec, calling

memcpy(0, 0, 0)

results in undefined behavior, because null pointers are considered "invalid values."

That said, I would be utterly astonished if any actual implementation of memcpy broke if you did this, since most of the intuitive implementations I can think of would do nothing at all if you said to copy zero bytes.

ζ澈沫 2024-10-27 18:07:45

只是为了好玩,gcc-4.9 的发行说明表明其优化器利用了这些规则,例如可以删除条件语句,其中

int copy (int* dest, int* src, size_t nbytes) {
    memmove (dest, src, nbytes);
    if (src != NULL)
        return *src;
    return 0;
}

then 在 copy(0,0,0) 被调用(参见 https://gcc.gnu.org/gcc-4.9/porting_to. html)。

我对 gcc-4.9 的行为有些矛盾;该行为可能符合标准,但能够调用 memmove(0,0,0) 有时是对这些标准的有用扩展。

Just for fun, the release-notes for gcc-4.9 indicate that its optimizer makes use of these rules, and for example can remove the conditional in

int copy (int* dest, int* src, size_t nbytes) {
    memmove (dest, src, nbytes);
    if (src != NULL)
        return *src;
    return 0;
}

which then gives unexpected results when copy(0,0,0) is called (see https://gcc.gnu.org/gcc-4.9/porting_to.html).

I am somewhat ambivalent about the gcc-4.9 behaviour; the behaviour might be standards compliant, but being able to call memmove(0,0,0) is sometimes a useful extension to those standards.

北方。的韩爷 2024-10-27 18:07:45

您还可以考虑 Git 2.14.x(2017 年第 3 季度)中看到的 memmove 用法,

请参阅 提交 168e635(2017 年 7 月 16 日),以及提交 1773664, 提交 f331ab9, 提交 5783980(2017 年 7 月 15 日),作者: René Scharfe (rscharfe)
(由 Junio C Hamano -- gitster -- 合并于 提交 32f9025,2017 年 8 月 11 日)

它使用 辅助宏 MOVE_ARRAY 计算尺寸
基于我们指定的元素数量并支持NULL
当该数字为零时的指针。
原始 memmove(3)使用 NULL 调用可以
导致编译器(过度急切地)优化以后的 NULL 检查。

MOVE_ARRAY 添加了一个安全且方便的帮助器,用于移动可能重叠的数组条目范围。
它推断元素大小,自动安全地相乘以获得以字节为单位的大小,通过比较元素大小进行基本类型安全检查,与 memmove(3) 不同,它支持 NULL当且仅当有 0 个元素要移动时指针。

#define MOVE_ARRAY(dst, src, n) move_array((dst), (src), (n), sizeof(*(dst)) + \
    BUILD_ASSERT_OR_ZERO(sizeof(*(dst)) == sizeof(*(src))))
static inline void move_array(void *dst, const void *src, size_t n, size_t size)
{
    if (n)
        memmove(dst, src, st_mult(size, n));
}

示例

- memmove(dst, src, (n) * sizeof(*dst));
+ MOVE_ARRAY(dst, src, n);

它使用 < a href="https://github.com/git/git/blob/89c855ed3cbe64bea93ea081d0e96077e9ee8517/git-compat-util.h#L46-L59" rel="nofollow noreferrer">宏 BUILD_ASSERT_OR_ZERO 断言构建时依赖关系,作为表达式(@cond 是必须为 true 的编译时条件)。
如果条件不成立,或者编译器无法计算,编译将会失败。

#define BUILD_ASSERT_OR_ZERO(cond) \
(sizeof(char [1 - 2*!(cond)]) - 1)

示例:

#define foo_to_char(foo)                \
     ((char *)(foo)                     \
      + BUILD_ASSERT_OR_ZERO(offsetof(struct foo, string) == 0))

正如 user16217248评论

在 C11 中,BUILD_ASSERT_OR_ZERO 宏不是必需的,因为我们有 static_assert()

You can also consider this usage of memmove seen in Git 2.14.x (Q3 2017)

See commit 168e635 (16 Jul 2017), and commit 1773664, commit f331ab9, commit 5783980 (15 Jul 2017) by René Scharfe (rscharfe).
(Merged by Junio C Hamano -- gitster -- in commit 32f9025, 11 Aug 2017)

It uses an helper macro MOVE_ARRAY which calculates the size
based on the specified number of elements for us and supports NULL
pointers when that number is zero.
Raw memmove(3) calls with NULL can
cause the compiler to (over-eagerly) optimize out later NULL checks.

MOVE_ARRAY adds a safe and convenient helper for moving potentially overlapping ranges of array entries.
It infers the element size, multiplies automatically and safely to get the size in bytes, does a basic type safety check by comparing element sizes and unlike memmove(3) it supports NULL pointers iff 0 elements are to be moved.

#define MOVE_ARRAY(dst, src, n) move_array((dst), (src), (n), sizeof(*(dst)) + \
    BUILD_ASSERT_OR_ZERO(sizeof(*(dst)) == sizeof(*(src))))
static inline void move_array(void *dst, const void *src, size_t n, size_t size)
{
    if (n)
        memmove(dst, src, st_mult(size, n));
}

Examples:

- memmove(dst, src, (n) * sizeof(*dst));
+ MOVE_ARRAY(dst, src, n);

It uses the macro BUILD_ASSERT_OR_ZERO which asserts a build-time dependency, as an expression (with @cond being the compile-time condition which must be true).
The compilation will fail if the condition isn't true, or can't be evaluated by the compiler.

#define BUILD_ASSERT_OR_ZERO(cond) \
(sizeof(char [1 - 2*!(cond)]) - 1)

Example:

#define foo_to_char(foo)                \
     ((char *)(foo)                     \
      + BUILD_ASSERT_OR_ZERO(offsetof(struct foo, string) == 0))

As noted by user16217248 in the comments:

In C11 the BUILD_ASSERT_OR_ZERO macro is not necessary because we have static_assert()

往日情怀 2024-10-27 18:07:45

未定义的行为可能会导致真正的问题。我刚刚遇到一个案例,确实如此。我使用 g++ 编译选项 -fsanitize=address,undefined 来找到它。就我而言,我有一个单元测试和生产代码,它们使用相同的参数调用相同的函数。单元测试通过,生产代码失败。

我必须更改的代码来自 miniz-cpp。我将 mz_zip_array_push_back 函数从 更改为

  memcpy(..., n * pArray->m_element_size);

  if (n > 0)
    memcpy(..., n * pArray->m_element_size);

它解决了问题。

The undefined behavior can cause real issue. I just ran into a case where it did. I used the g++ compile option -fsanitize=address,undefined to find it. In my case, I have a unit test and production code that are calling the same function with the same arguments. The unit test passed and the production code failed.

The code I had to change is from miniz-cpp. I changed the mz_zip_array_push_back function from

  memcpy(..., n * pArray->m_element_size);

to

  if (n > 0)
    memcpy(..., n * pArray->m_element_size);

and it resolved the issue.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文