当前位置: 首页 > 知识库问答 >
问题:

C #中的结构偏移量和指针安全

米承嗣
2023-03-14

这个问题是关于使用带结构偏移量的指针算法派生的指针。

考虑以下程序

#include <cstddef>
#include <iostream>
#include <new>

struct A {
  float a;
  double b;
  int c;
};

static constexpr auto off_c = offsetof(A, c);

int main() {
  A * a = new A{0.0f, 0.0, 5};
  char * a_storage = reinterpret_cast<char *>(a);
  int * c = reinterpret_cast<int *>(a_storage + off_c));

  std::cout << *c << std::endl;

  delete a;
}

这个程序似乎可以工作,并在我使用默认设置和C 11标准测试的编译器上给出预期的结果。

(一个密切相关的程序,其中我们使用< code>void *而不是< code>char *和< code>static_cast而不是< code>reinterpret_cast,并没有被普遍接受。< code>gcc 5.4发布了一个关于带有void指针的指针算法的警告,而< code>clang 6.0指出带有< code>void *的指针算法是错误的。)

根据C标准,该程序是否具有明确定义的行为?

答案取决于实现是放宽了指针安全还是严格了指针安全([basic.stc.dynamic.safety])?

共有3个答案

文国发
2023-03-14

你应该检查一下你的假设。

假设 #1) 偏移量给出正确的偏移量(以字节为单位)。只有当类被认为是“标准布局”时,这才得到保证,它有许多限制,例如没有虚拟方法,避免多重继承等。在这种情况下,它应该没问题,但一般来说你不能确定。

假设#2)一个字符与一个字节大小相同。在C语言中,这是被定义的,所以你是安全的。

假设 #3) 偏移量给出了从指向类的指针的正确偏移量,而不是从数据开头开始的正确偏移量。这与#1基本相同,但vtable肯定是一个问题。同样,仅适用于标准布局。

隆礼骞
2023-03-14

它在您的特定示例中是安全的,但只是因为您的结构是标准布局,您可以使用 std::is_standard_layout

尝试将此应用于结构,例如:

struct A {
  float a;
  double b;
  int c;
  std::string str;
};

将是非法的,即使字符串超过了相关的结构部分。

编辑

这是我关心的问题:在3.7.4.3[basic.stc.dynamic.safety]中,它说只有当(条件)并且如果我们有严格的指针安全,那么指针不是来自这样的地方,指针才是无效的。在5.7指针算术中,它说我可以在数组中执行通常的算术,但我没有看到任何东西告诉我结构偏移算术是可以的。我想弄清楚这是否与我认为的方式无关,或者在假设的“严格”impls中,结构偏移算术是否不可以,或者我是否看错了5.7(n4296)

当您执行指针算术时,您正在对char的数组执行它,其大小至少为sizeof(A),所以没关系。

然后,当你投回第二个成员时,你被覆盖在(2.4):内:

-安全派生指针值的定义明确的指针转换结果(4.10、5.4);

卫和洽
2023-03-14

你的代码中没有根本性的错误。

如果< code>A不是普通的旧数据,那么上面是UB(在C 17之前)和有条件支持的(在C 17之后)。

您可能想将char*int*替换为 ,但这是一个风格问题。

请注意,指向成员的指针以类型安全的方式执行完全相同的操作。大多数编译器实现指向成员的指针...作为类型中成员的偏移量。然而,它们确实在任何地方工作,甚至在非豆荚结构上也是如此。

旁白:我没有看到offsetof在标准中是instexpr的保证。;)

在任何情况下,更换:

static constexpr auto off_c = offsetof(A, c);

随着

static constexpr auto off_c = &A::c;

  auto* a_storage = static_cast<char *>(a);
  auto* c = reinterpret_cast<int *>(a_storage + off_c));

随着

  auto* c = &(a->*off_c);

用C方式来做。

 类似资料:
  • 我不确定安德烈·卡隆在这里是什么意思: C中的虚拟函数 ...其中一些代码依赖于(正式的)非标准行为,这些行为“恰好”在大多数编译器上工作。主要问题是代码假设 < code>m的类型为< code>struct meh *。类型为< code>struct foo *的对象f通过向< code>struct meh *的强制转换被赋给m。< code>struct meh具有< code>stru

  • 本文向大家介绍详解C++中的指针结构体数组以及指向结构体变量的指针,包括了详解C++中的指针结构体数组以及指向结构体变量的指针的使用技巧和注意事项,需要的朋友参考一下 C++结构体数组 一个结构体变量中可以存放一组数据(如一个学生的学号、姓名、成绩等数据)。如果有10个学生的数据需要参加运算,显然应该用数组,这就是结构体数组。结构体数组与以前介绍过的数值型数组的不同之处在于:每个数组元素都是一个结

  • 我正在学习链表,以及如何在C中使用结构和指针创建链表。下面我举一个例子。据我所知,被调用的将头节点所在的结构的开始内存位置作为参数传递。push()函数的参数将结构节点作为指向指针的指针,因此它作为引用传递,而不是实际副本。因此,我们的的第一个指针只是指向头部节点的内存位置的指针,第二个指针指向该值,该值是头部节点指向的下一个内存位置。我们通过为结构节点分配一些内存,在结构节点内创建一个名为new

  • 本文向大家介绍详解C语言中的常量指针和指针常量,包括了详解C语言中的常量指针和指针常量的使用技巧和注意事项,需要的朋友参考一下 概述 对于新手来说,指针在c语言里总是一个非常难以理解的概念。在这篇文章中,我们将解释常量指针,指针常量,const pointer to const(ps:楼主以为这可以翻译成指向常量的常量指针)的区别 常量指针 让我们先来理解什么是常量指针。常量指针是指指针指向的地址

  • 主要内容:获取结构体成员,结构体指针作为函数参数当一个 指针变量指向结构体时,我们就称它为 结构体指针。 C语言结构体指针的定义形式一般为: struct 结构体名 *变量名; 下面是一个定义结构体指针的实例: 也可以在定义结构体的同时定义结构体指针: 注意,结构体变量名和数组名不同,数组名在表达式中会被转换为数组指针,而结构体变量名不会,无论在任何表达式中它表示的都是整个集合本身,要想取得结构体变量的地址,必须在前面加 ,所以给 pstu 赋

  • 在这两个示例中,通过偏移其他成员的指针来访问结构成员是否会导致未定义/未指定/实现定义的行为? C11§6.7.2.1第14段似乎表明,这应该是实施定义: 结构或联合对象的每个非位字段成员都以适合其类型的实现定义方式对齐。 后来又说: 结构对象中可能有未命名的填充,但在其开头没有。 但是,如下所示的代码似乎相当常见: 该标准似乎保证 与 和< code >( 原始应用程序正在考虑从一个结构字段到另