【问题标题】:Accessing bytes of an object in C在 C 中访问对象的字节
【发布时间】:2018-08-16 00:00:56
【问题描述】:

很遗憾,我没有找到类似 std-discussion 的 ISO C 标准,所以我会在这里问。

在回答问题之前,请确保您熟悉指针出处的概念(请参阅DR260 和/或http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2263.htm)。

6.3.2.3(指针)第 7 段说:

当指向对象的指针转换为指向字符类型的指针时, 结果指向对象的最低寻址字节。的连续递增 结果,直到对象的大小,产生指向对象剩余字节的指针。

问题是:

  1. “指向对象的指针”是什么意思?这是否意味着如果我们想要获得指向T 类型对象的最低寻址字节的指针,我们应该将类型为 cvT* 的指针转换为指向字符类型,并将指针转换为void,从T* 类型的指针获得,不会给我们想要的结果吗?或者“指向对象的指针”是指针的值,它从指针出处开始并转换为 void* 不会改变值(类似于它最近在 C++17 中的形式化方式)?

  2. 为什么段落明确提到增量?这是否意味着添加大于 1 的值是未定义的?这是否意味着递减(在我们多次递增结果以便我们不会超出下对象边界之后)是未定义的?简而言之:组成对象的字节序列是数组吗?

【问题讨论】:

  • "指向对象的指针" --> 指向某个非函数的指针,非void
  • “强制转换为 void* 不会改变值”几乎是正确的。 编码可能会改变,但它会等同于原始。类似于10 == 10.0,但它们有不同的编码。指针具有与整数不同的属性。这里有很多问题 - 至少 7 个。也许可以减少它。
  • 虽然一个理性的人可能质疑“递增 -1”(而不是“递减 1”)的有效性,但我认为可以肯定地说您可以递增任何正数。 i+=5;//a stupid but accurate comment here is 'increment by 5'。关于您的最后一个问题,您是否特别想知道字节是否是连续的并且是否可以被视为 C 数组,或者您是否正在驾驶不同的东西?
  • @zzxyz 我不认为你可以增加任何正数。该标准在 6.5.2.4(后缀)和 6.5.3.1(前缀)中定义了增量。我认为为标准中定义的术语创造自己的含义是不正确的。
  • @M.M 如果我理解正确,您是想说省略明确的行为定义并不会使其未定义。好吧,标准明确说明了相反的情况。

标签: c language-lawyer


【解决方案1】:

指针加法的一般描述表明,对于指针p 和有符号整数xy 的任何值/类型,其中((ptr+x)+y)(x+y) 均由标准定义,(ptr+(x+y)) 将行为等同于((ptr+x)+y)。虽然可能会争辩说该标准没有明确说将指针递增五次将等同于加 5,但标准中没有任何内容表明质量实现不应该是预计会以这种方式行事。

请注意,该标准的作者并没有试图使其成为“语言律师证明”。此外,他们认为没有人会关心明显较差的实现是否“符合”。仅当对象的字节按顺序访问时才可靠工作的实现将不如支持可靠索引的实现通用,同时没有提供合理的优势。因此,标准应该没有需要强制支持索引,因为任何试图产生高质量实现的人都会支持它无论标准是否强制

当然,1990 年代的程序员——甚至是标准的作者自己——期望质量编译器能够可靠地处理某些构造,但今天的一些“聪明”编译器却没有。这是否意味着这种期望是不合理的,或者它们在应用于质量编译器时是否仍然准确,这是一个见仁见智的问题。在这种特殊情况下,我认为正索引应该表现得像重复递增的含义足够强大,以至于我不希望编译器编写者有其他争论,但我不能 100% 确定没有编译器会“聪明”/钝到可以看类似的东西:

int test(unsigned char foo[5][5], int x)
{
  foo[1][0] = 1;

  // Following should yield a pointer that can be used to access the entire
  // array 'foo', but an obtuse compiler writer could perhaps argue that the
  // code is converting the address of foo[0] into a pointer to the first
  // element of that sub-array, and that the resulting pointer is thus only
  // usable to access items within that sub-array.

  unsigned char *p = (unsigned char*)foo;

  // Following should be able to access any element of the object [i.e. foo]
  // whose address was taken

  p[x] = 2;

  return foo[1][0];
}

并决定它可以跳过foo[1][0] 的第二次读取,因为p[x] 不可能访问第一行之外的foo 的任何元素。然而,我想说,程序员不应该试图绕过破坏者编写会以这种方式运行的编译器的可能性进行编码。任何程序都无法抵御编写迟钝但“符合”编译器的破坏者的攻击,并且程序可以被此类破坏者破坏的事实不应被视为缺陷。

【讨论】:

  • 很好的答案。这里有一个最近的相关问题(针对 C++)。在这种情况下,最大的区别是行为被明确称为未定义:stackoverflow.com/questions/51623643/…
  • @zzxyz:我看不出delete 的问题与访问对象的字节有什么关系?你复制错链接了吗?
  • 不,我没有,但我应该澄清一下我所看到的相似之处,即:常识告诉您编译器应该将指针视为“只是一个整数”(或者可能是“只是一个void*”)不能保证。一些 cmets 和答案特别指出了这一点(不管不存在自定义析构函数)。
  • @zzxyz:如果作者试图列举质量编译器应该支持的所有行为保证,包括那些(1)所有现有编译器都支持它们的行为保证,C89 标准会更大一些; (2) 支持本标准其他部分的最简单方法也支持相关保证。作者认为没有人会关心故意表现得不太理想的编译器是否会“符合”标准,因为他们认为没有人会尝试使用标准来证明他们的行为是正当的。
【解决方案2】:

获取一个非字符 c 对象并创建一个指向它的指针,即

int obj;
int *objPtr = &obj;

将指向对象的指针转换为指向char的指针:

char *charPtr = (char *)objPtr;

现在,charPtr 指向最低字节或 int obj。 增加它:

charPtr++;

现在它指向对象的下一个字节。依此类推,直到达到对象的大小:

int i;
for (i = 0; i < sizeof(obj); i++) 
    printf("%d", *charPtr++);

【讨论】:

  • 如果改成char *charPtr = (char *)((void*)objPtr);charPtr会指向哪里?为什么?
  • 这不是任何问题的答案。
  • 这个问题不是关于访问代表对象的字节。它询问 C 标准语言的具体细节。例如,如果将指向对象的指针转换为指向 void 的指针,它仍然是“指向对象的指针”吗?也就是说,对于 C 2018 6.3.2.3 7 而言,void * 是否是指向与原始指针相同的对象的指针,无论类型如何? (我们不是在问它是否可以在它是 void * 时被取消引用,只是它是否仍然符合 6.3.2.3 7 中指定的转换语义。)
  • 更具体地说,鉴于上面初始化的charPtr,标准明确表示我们可以通过*charPtr++重复访问字节。但它是否说我们可以通过charPtr[2] 访问一个字节?其中没有连续的增量,因此 6.3.2.3 7 中的语言没有明确说明。这是问题要解决的问题之一。
猜你喜欢
  • 2019-09-03
  • 1970-01-01
  • 2011-05-24
  • 2018-04-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-01-14
  • 1970-01-01
相关资源
最近更新 更多