【问题标题】:Normal Structure Variable A decay Pointer or A Stack Frame正常结构变量 A衰变指针或堆栈帧
【发布时间】:2021-10-25 03:32:50
【问题描述】:
struct student  
{  
      int roll_no;  
      int physics;  
      int chem;  
      float percentage;  
};

int main()  
{  
    struct student s = {1, 96, 98, 97.00};  
    printf("%d", *((int *) &s + 1));  
}

在上面的代码中,我们知道's'是一个结构变量,我的问题是什么

's' 是衰减指针还是为结构分配了单独的堆栈帧,如果它是衰减指针,我们不必将 '&' 与 's' 放在一起,所以我猜它必须是一个堆栈帧,因为它的行为就像一个普通变量。

如果我们打印's',第一个成员的值就会被打印出来,如果我们打印'&s',第一个成员的地址就会被打印出来。

如果不是这个,请帮助我们理解。

还有,

如果 Char 数组是结构的成员,如何访问,试过这个 *((char*)&s + offset_value); 不起作用。

您的帮助将不胜感激。

【问题讨论】:

  • 你能不能只用'.'访问字段而不是指针?
  • 强制转换结构是 C 中未定义的行为。这意味着:“(int *) s”是未定义的行为,因为“s”不是原始类型(char、short、int 等) .但是, (int *) &s 非常好,它会为您提供结构包含的第一个整数。
  • "'s' 是衰减指针还是为结构分配了单独的堆栈帧,如果它是衰减指针,我们不必将 '&' 与 's ' 所以我猜它一定是一个堆栈帧,因为它的行为就像一个正常的变量。”这没有任何意义。 s 是一个结构。它不是一个数组,所以它不会“衰减”。它不是“堆栈框架”。您似乎混淆了许多非常不相关的术语。
  • @tstanisl:相比之下,*((int *) &s + 1) 的行为没有定义。如上,这相当于*(&s.roll_no + 1)&s.roll_no + 1 已定义;它是一个指向s.roll_no 末尾的指针。但是,没有定义用* 解引用它,因为C 2018 6.5.6 8 表示如果+ 的结果像这样指向末尾,“它不应用作一元* 的操作数被评估的运算符。”换句话说,即使我们可以推断出指针指向的内存中的内容是另一个int,但有一个明确的规则说我们不能在上面使用*
  • @EricPostpischil,我仍然有疑问。我不认为*(&s.roll_no + 1) 等同于*((int*)&s + 1)。第一个产生一个源自s.roll_no 对象的指针,即int。因此,增加的指针确实不能被取消引用。另一方面,((int*)&s + 1) 源自对象s,因此生成的指针不会“溢出”原始对象。假设((int*)&s + 1) 无效,memcpy 会使用这样的指针吗?那么(int*)((char*)&s + sizeof(int)) 是否有效?

标签: c


【解决方案1】:

's' 是否是衰减指针……

s 是一个结构。结构不会像数组那样自动转换为指针。 (有些人说“腐烂”,但这是一个口语化术语,并不准确。)

…或者一个单独的堆栈框架正在为结构分配,…

如您在main 中显示的那样定义s,编译器会自动为其分配空间。在常见的 C 实现中,编译器为此使用堆栈空间(除了优化可能导致替代方案,包括使用处理器寄存器,将结构的使用合并到其他表达式中,或完全消除部分或全部结构)。如果编译器确实为s 使用了堆栈空间,它会为main 使用堆栈框架内的空间;它不分配单独的堆栈帧。

(通常,堆栈帧是一个函数使用的堆栈空间,有时会进行一些修改,例如不调用其他例程并且可能无法建立完整堆栈帧的“叶”函数。)

printf("%d", *((int *) &s + 1));

*((int *) &s + 1) 不是正确的 C 代码,因为它的行为不是由 C 标准定义的,也可能不是由您的编译器定义的。

&s 是结构体s 的地址。当我们将它转​​换为(int *) 时,它给出了结构的第一个成员的地址s.roll_no。这是因为 C 标准给了我们一条规则,说我们可以将指向结构的指针转换为指向其第一个成员的指针。 C 2018 6.7.2.1 15 说:

…一个指向结构对象的指针,经过适当转换,指向它的初始成员(或者如果该成员是位域,则指向它所在的单元),反之亦然…

然后你给这个指针加 1,(int *) &s + 1。这也有明确的行为。从上面的(int *) &s 指向s.roll_no,所以它等价于&s.roll_no。那么(int *) &s + 1 等价于&s.roll_no + 1。 C 2018 6.5.6 指定了加法会发生什么。我将省略规则的细节,但本质上&s.roll_no + 1 已定义并生成一个指向s.roll_no 之外的指针。

但是,即使结构的下一个成员 physics 刚好超出 s.roll_no1,C 标准也没有定义当 * 应用于它时会发生什么。这是因为 C 2018 6.5.6 8 说:

如果 [加法] 的结果指向数组对象的最后一个元素之后的一个位置,则不应将其用作计算的一元 * 运算符的操作数。

在 6.5.6 的上下文中,s.roll_no 是一个仅包含一个元素的“数组对象”,&s.roll_no + 1 指向其最后一个元素之外的一个元素。所以*(&s.roll_no + 1) 和等价的*((int *) &s + 1) 违反了这条规则。 C 2018 4 2 表示当违反这样的规则时,行为未定义:

如果违反了出现在约束或运行时约束之外的“应”或“不应”要求,则行为未定义……

脚注

1 这不能保证,因为 C 实现允许在结构中的成员之间插入填充。然而,典型的 C 实现不会在相同类型的成员之间插入填充,因为它不需要对齐,这可以在编译时检查。但即使没有填充,上述语句也适用。

【讨论】:

    【解决方案2】:

    结构参数按值传递,除非使用& 运算符。数组参数作为指针传递。局部变量位于堆栈帧(或寄存器)中。

    您可能会发现 offsetof 宏很有趣

    #define offsetof(S, F) (((char *)&((S *)0)->F - (char *)0))
    

    【讨论】:

    • 非常感谢@dirck,你是生命的救星
    • 一个堆栈帧中可以包含多个数据。就像一个结构,我们可以确认一下吗?
    • 通常,函数的调用会为所有局部变量和参数创建一个(单个)堆栈帧——帧指针一侧的参数和另一侧的局部变量。如果一个函数没有局部变量和参数,它可能不会显式地创建一个框架。编译器也可以对此进行优化。
    • 好吧,结构变量也是堆栈帧的一部分,所以你能说一下它的行为。
    • offsetof 已被 C 标准要求,可在 stddef.h 中找到。没有必要重新发明它
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-01-03
    • 2019-01-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-18
    相关资源
    最近更新 更多