【问题标题】:&((struct name *)NULL -> b) in printf statement [duplicate]&((struct name *)NULL -> b) 在 printf 语句中 [重复]
【发布时间】:2015-01-10 08:56:13
【问题描述】:

我在一本书中找到了这个代码示例,但我无法理解 printf 语句中的表达式。 该程序编译成功,输出为 4。 请指教...

void main(){
    unsigned char c;

    typedef struct name {
      long a;
      int b;
      long c;
    }r;

    r re = {3,4,5};
    r *na=&re;

    printf("%d",*(int*)((char*)na + (unsigned int ) & (((struct name  *)NULL)->b)));
}

【问题讨论】:

  • 您知道main 在 C(和 C++)中的返回类型为 int
  • 这个确切的程序是3年前发布的,我猜是一本普通的书!

标签: c struct casting offsetof


【解决方案1】:

让我们从最后一行开始:

printf("%d",*(int*)((char*)na + (unsigned int ) & (((struct name  *)NULL)->b)));

让我们解释一下:

(unsigned int ) & ((    (struct name  *)NULL)->b )

实际上是将& (( (struct name *)NULL)->b ) 转换为unsigned int

& (( (struct name *)NULL)->b ) 是地址(即它给出了一个指向的指针):

((  (struct name  *)NULL)->b )

这实际上是b(如name.b)与NULL(0)的偏移量,它是4字节(假设long是4字节)并转换为int指针,给你2(假设 int 是 2 个字节)。

如果不是NULL,而是指向0xFFFF0000 的指针,那么&(ptr->b) 将是0xFFFF0002。但它更像&(0 -> b) 所以它是0x00000002

所以,(unsigned int ) & (( (struct name *)NULL)->b ) == 2(或者可能是 1,也可能是 4,具体取决于机器)。

剩下的很简单:*(int*)((char*)na + 2 将指向re->b所以它应该打印 4(代码中已经初始化的内容,r re ={3,4,5};)。

P.S:即使(unsigned int ) & (( (struct name *)NULL)->b ) != 2(可能是 1、4 或 8) - 它仍然应该打印 4,因为它使用相同的偏移量来获取值。

【讨论】:

  • 请注意,此构造调用 UB。
  • @Deduplicator (( (struct name *)NULL)->b ) 是未定义的行为吗?它实际上对我来说似乎定义得很好。你能给我指向一个指定它的链接吗?
  • @MarkSegal 很好的解释...非常感谢...
  • @MarkSegal:它正在对空指针进行指针运算,这完全是非法的。指针算术仅针对指向有效对象的指针定义,并且仅在它不偏离基础对象边界的情况下(尽管明确允许指针通过对象)。
  • @MarkSegal:它可能是未指定的未定义行为,即使它实际上在任何地方都有效。
【解决方案2】:

rer类型的局部变量,即struct name;它通常分配在call stack

na 是指向re 的指针。

(unsigned int) & (((struct name *)NULL)->b) 可能是undefined behavior(但我不确定),但大多数编译器会将其编译为字段b 的偏移量-in bytes-(就像offsetof 一样,请参阅offsetof(3))。在我的机器上可能是 8。

(char*)na +上面的偏移量往往和&re.b是同一个地址

您取消引用该指针,实际上是&re.b

我觉得您的代码可能不符合标准(请参阅 this answer 以获得一些论据;可能存在假设的机器和 C 实现,其中 NULL 不是全零位字,我不知道这样的实现) ,但在我知道的所有机器上,它应该打印字段re.b的值

【讨论】:

  • 感谢@Basile .... 让我们继续使用我们所知道的机器... :)
  • @HimanshuSourav: c-faq.com/null/machexamp.html 只是让您了解更多机器 ;-)
  • @Deduplicator:但这些 XX 世纪的旧机器只存在于博物馆中!
  • 差不多。这就是为什么在实践中大多数情况下可以忽略非所有位为零的空指针的原因。但是,指针算术中的 UB 仍然可以在现代机器上咬你,这取决于(主要)你的实现。
【解决方案3】:

代码:

(unsigned int ) & (((struct name  *)NULL)->b))

旨在获取变量b 距离struct name 开头多远的计数(以字节为单位)。

有一种标准方法可以做到这一点:offsetof(struct name, b);。编写此代码的人要么不知道 offsetof,要么正试图教一些东西(尽管这可能是盲人引导盲人的情况)。

代码通过取消引用空指针导致未定义的行为,但是常见的编译器可能会在不触发错误的情况下接受它,这可能是因为编译器开发人员知道存在这样的现有代码。


其余的代码很简单;它指向结构的开始;前进这么多字节,并从该位置读取一个 int ;这当然和直接阅读b一样。

【讨论】:

  • 从技术上讲,代码不是取消引用NULL 指针。但我仍然觉得这是未定义的行为。
  • @BasileStarynkevitch 我已经提出了一个新问题,即这是否真的是 UB,尽管在第一次阅读 C11 时看起来不太好
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-01-10
  • 2021-10-01
  • 1970-01-01
  • 2015-08-31
  • 1970-01-01
  • 2020-03-27
  • 2014-01-01
相关资源
最近更新 更多