Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >问答首页 >允许struct字段溢出到下一个字段

允许struct字段溢出到下一个字段
EN

Stack Overflow用户
提问于 2020-07-02 00:54:16
回答 5查看 673关注 0票数 10

考虑以下简单示例:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
struct __attribute__ ((__packed__)) {
 int code[1];
 int place_holder[100];
} s;

void test(int n)
{
 int i;

 for (i = 0; i < n; i++) {
  s.code[i] = 1;
 }
}

for-循环正在写入大小为1的字段code。在code之后的下一个字段是place_holder

我预计,在n > 1的情况下,对code数组的写将溢出,而1将被写入place_holder

然而,在用-O2编译时(在gcc 4.9.4上,但也可能在其他版本上),会发生一些有趣的事情。

编译器标识代码可能溢出数组code限制循环展开为1迭代

在使用-fdump-tree-all编译和查看最后一个树传递(“t.optimized”)时,很容易看出这一点:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
;; Function test (test, funcdef_no=0, decl_uid=1366, symbol_order=1)

Removing basic block 5
test (int n)
{
  <bb 2>:
  # DEBUG i => 0
  # DEBUG i => 0
  if (n_4(D) > 0)
    goto <bb 3>;
  else
    goto <bb 4>;

  <bb 3>:
  s.code[0] = 1;
  # DEBUG i => 1
  # DEBUG i => 1

  <bb 4>:
  return;

}

因此,在本例中,编译器将循环完全展开为一个迭代。

我的问题是:

  1. 从C规范的角度来看,从一个struct成员到另一个结构成员的溢出(故意)是非法的还是未定义的行为? 让我们假设我知道内存中的结构布局,并且知道当故意溢出code数组时我在做什么。
  2. 在这种情况下,有没有办法阻止gcc展开循环?我知道我可以完全防止循环展开,但是我仍然对其他情况下的循环展开感兴趣。我还怀疑编译器所做的分析可能会影响循环展开以外的传递。 gcc假设我在访问数组时不会溢出,所以我真正想要的是告诉编译器不要接受这个假设(通过提供一些编译器选项)。

我知道编写这样的代码从一个字段溢出到另一个字段是一种不好的做法,我不打算编写这样的代码。

我还知道将数组(可能为零大小)作为最后一个结构字段以允许其溢出的做法,编译器很好地支持这一点,而在这种情况下,数组code不是最后一个字段。

所以这不是一个“如何修复代码”的问题,而是一个理解编译器假设并影响它们的问题。

当我观察到已经以这种方式编写的现有代码时,出现了这些问题,并对其进行了调试,以找出它为什么不像最初的开发人员所期望的那样运行。

风险在于代码中还有其他地方存在这样的问题。静态分析工具可以帮助查找,但我也想知道是否有一种方法可以让编译器容忍这样的代码,并仍然生成我们期望的结果。

更新

我对上面的问题(1)有了明确的答案,但问题(2)没有得到明确的回答。

  • gcc可以通过一些编译选项将其作为扩展吗?
  • 有什么办法至少在gcc确认后得到警告吗?(它通过优化事物来明确地识别它)。 这对于识别大型现有代码库中的此类情况非常重要。
EN

回答 5

Stack Overflow用户

发布于 2020-07-02 01:23:56

从C规范的角度来看,从一个struct成员到另一个结构成员的溢出(故意)是非法的还是未定义的行为?

这是未定义的行为。arr[i]运算符是*(arr + i)周围的语法糖。因此,数组访问归结为指针算术的二进制+操作符,C17 6.5.6加法运算符,从§7和§8开始:

为了这些运算符的目的,指向非数组元素的对象的指针的行为与指向长度为1的数组的第一个元素的指针相同,其元素类型为对象的类型。 将具有整数类型的表达式添加到指针或从指针中减去时,结果具有指针操作数的类型。/-/ 如果指针操作数和结果都指向同一个数组对象的元素,或者指向数组对象的最后一个元素,则计算值不会产生溢出;否则,行为将未定义。如果结果指向数组对象的最后一个元素,则不应将其用作求值的一元*操作符的操作数。

正如您注意到的,优化编译器可能利用这些规则来生成更快的代码。

在这种情况下,有没有办法阻止gcc展开循环?

有一个特殊的例外规则可以使用,C17 6.3.2.3/7:

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

此外,严格的混叠不适用于字符类型,因为C17 6.5§7中的另一条特殊规则

对象的存储值只能由具有下列类型之一的lvalue表达式访问:字符类型。

这两条特殊规则和谐共存。因此,假设我们在指针转换过程中没有弄乱对齐等,这意味着我们可以这样做:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
unsigned char* i;
for(i = (unsigned char*)&mystruct; i < (unsigned char*)(&mystruct + 1); i++)
{
  do_something(*i);
}

然而,这可能读取填充字节等,因此它是“实现定义的”。但从理论上讲,您可以访问每个字节的struct字节,并且只要结构偏移量是按字节计算的,就可以以这种方式遍历结构的多个成员(或任何其他对象)。

据我所知,这个看上去很可疑的代码应该有很好的定义:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <stdint.h>
#include <string.h>
#include <stdio.h>

struct __attribute__ ((__packed__)) {
 int code[1];
 int place_holder[100];
} s;

void test(int val, int n)
{
  for (unsigned char* i = (unsigned char*)&s; 
       i < (unsigned char*)&s + n*sizeof(int); 
       i += _Alignof(int)) 
  {
    if((uintptr_t)i % _Alignof(int) == 0) // not really necessary, just defensive prog.
    {
      memcpy(i, &val, sizeof(int));
      printf("Writing %d to address %p\n", val, (void*)i);
    }
  }
}

int main (void)
{
  test(42, 3);
  printf("%d %d %d\n", s.code[0], s.place_holder[0], s.place_holder[1]);
}

这在gcc和clang (x86)上很好。效率有多高,那又是另外一个故事了。不过,请不要写这样的代码。

票数 6
EN

Stack Overflow用户

发布于 2020-07-02 01:11:02

从C规范的角度来看,从一个struct成员到另一个结构成员的溢出(故意)是非法的还是未定义的行为?

访问超出范围的数组是一种未定义的行为。来自C11 J.2

在下列情况下未对行为进行定义: ..。 数组下标超出了范围..。在这种情况下,有没有办法阻止gcc展开循环?

别名code,带有volatile指针。但是,即使使用中介指针似乎也是有效的。螺栓连接

票数 1
EN

Stack Overflow用户

发布于 2020-07-02 03:12:23

1.问题:

“从C规范的角度来看,是否(故意)从一个结构成员溢出到下一个非法或未定义的行为?”

未定义行为。C标准规定(强调我的标准):

“后缀表达式后面加上方括号中的表达式[]是数组对象元素的下标指定。下标运算符[]的定义是E1[E2](*((E1)+(E2)))相同。由于适用于二进制+运算符的转换规则,如果E1是数组对象(相当于数组对象的初始元素的指针),E2是整数,则E1[E2]指定E1E2-th元素(从零计数)。” 来源: ISO/IEC 9899:2018 (C18),§6.5.2.1/2“当一个具有整数类型的表达式添加到指针或从指针中减去时,结果具有指针操作数的类型。如果指针操作数指向数组对象的一个元素,并且数组足够大,则结果指向与原始元素偏移的元素,从而使结果数组元素和原始数组元素下标的差值等于整数表达式。换句话说,如果表达式P指向数组对象的i-th元素,则表达式(P) + N (等效地,N + (P))和(P) - N (其中N的值为n)分别指向数组对象的i+n-th和i−n-th元素,只要它们存在。此外,如果表达式P指向数组对象的最后一个元素,则表达式(P) + 1指向数组对象的最后一个元素,如果表达式Q指向数组对象的最后一个元素,则表达式(Q) - 1指向数组对象的最后一个元素。如果指针操作数和结果都指向同一个数组对象的元素,或者指向数组对象的最后一个元素,则计算值不会产生溢出;否则,行为将未定义。如果结果指向数组对象的最后一个元素,则不应将其用作计算的一元*运算符的操作数。 资料来源: ISO/IEC 9899:2018 (C18),§6.5.6/8。

非规范性附件J还就规范标准第6.5.6节规定:

J.2未定义行为 1在下列情况下,行为未予界定: ……

  • 数组下标超出了范围,即使一个对象显然可以用给定的下标访问(如给定声明a[1][7]的lvalue表达式int a[4][5]) (6.5.6)。

2.问题(加最新情况):

“在这种情况下,有没有办法阻止gcc展开循环?” “gcc能允许通过一些编译选项将其作为扩展吗?” “当gcc确认时,有没有办法至少得到警告?这对于在现有的大型代码库中识别此类情况非常重要。”

您可以尝试将像asm("");这样的空汇编代码函数放入循环中,如DenilsonáMaia,F.E.的这个答案所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 for (i = 0; i < n; i++) {
    s.code[i] = 1;
    asm("");
 }

或者#pragmatest函数周围,如这里,F.E.所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#pragma GCC push_options
#pragma GCC optimize ("O0")

void test(int n)
{
   int i;

   for (i = 0; i < n; i++) {
      s.code[i] = 1;
   }
}

#pragma GCC pop_options

以防止对该特定程序部件的优化,并使循环展开。

相关信息:

它并不能阻止循环展开,但是您可以使用AddressSanitizer,它也集成了LeakSanitizer,并且从4.8版就内置到GCC中,用来检测循环展开不工作的时间/访问非关联内存。

有关这方面的更多信息,您可以找到这里

编辑:正如您所说的目标实现是MIPS,您仍然可以使用瓦兰来检测内存泄漏。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/62692609

复制
相关文章
C#中的属性
属性在C#中很常用,但有部分开发人员对它既熟悉又陌生。概念上属性是将元数据关联到元素的方式。属性的使用方法我们在代码中经常肩见到,比如下面这样的:
喵叔
2021/12/22
1.8K0
WPF 关于将 ManipulationDeltaEventArgs 的 Manipulators 属性返回值修改为 ReadOnlyCollection 类型的提议
讨论的地方是: How about change the type of ManipulationDeltaEventArgs.Manipulators property to ReadOnlyCollection · Discussion #6249 · dotnet/wpf
林德熙
2022/03/28
1.1K0
C# 附加属性
附加属性我们早就使用过,常见的用于控件定位的Grid.Row,Grid.Column就是附加属性,那这个东西具体是什么意思呢?请设想这样的情景:一个学生,他在社团可以是社长,他在班内可以是班长,在赛场又可以是运动员,这些特定的属性,并不是每个学生都具有的,只有学生参与了或者在某个环境中,才具有这样的属性。那我们在定义学生类时,就不能把这些属性定义进去,为了解决这种在某些环境中才具有特定属性的情况,WPF引入了附加属性,附加属性就是一个对象可以被它外部的环境附加某些属性,而对象本身实际上不具有这样的属性。
zls365
2021/09/02
1.2K0
C# 附加属性
C#的Button.DialogResult属性[通俗易懂]
如果此属性的DialogResult不是设置为None,并且父窗体是通过ShowDialog方法显示的,则不必挂钩任何事件,单击按钮也可关闭父窗体。然后,该窗体的DialogResult属性将设置为该按钮被单击时的DialogResult。
全栈程序员站长
2022/06/25
9700
Objective-C中的属性机制
        Objective-C 2.0中的属性机制为我们提供了便捷的获取和设置实例变量的方式,也可以说属性为我们提供了一个默认的设置器和访问器的实现。在学习OC中属性之前我们先要知道为什么要为变量实现getter和setter方法,我们先来了解一下实例的作用域。     实例变量的作用域如下:         1. @public : 共有的,该实例变量谁都可以访问;         2.@protected :受保护的,该实例变量只能在该类和其子类内访问,父类protected的实例变量在子类中
lizelu
2018/01/12
1.3K0
C#中Dock属性的作用[通俗易懂]
当在C#项目开发中,在窗体界面的设置经常用到Dock属性值。这里一panel面板的Dock属性值为例描述其作用。
全栈程序员站长
2022/10/01
1.2K0
C#中Dock属性的作用[通俗易懂]
Word 技术篇-段落的前后间距单位磅改为行,行改为磅
很多时候,比如我们写论文,word的格式是有严格要求的,我们要一丝不苟的按照要求来。因为,这是一种规范,也是是一种礼仪。
小蓝枣
2020/09/23
1.8K0
Objective-C属性(property)的特性(attribute)
以下:「attribute(s)」,「特性」是指同一事物(都指@property后面括号内的单词)。
iOS Development
2019/02/14
2K0
Word文档段落的前后间距单位磅改为行,行改为磅方法演示
很多时候,比如我们写论文,word 的格式是有严格要求的,我们要一丝不苟的按照要求来。因为,这是一种规范,也是是一种礼仪。
小蓝枣
2023/05/27
1.8K0
Word文档段落的前后间距单位磅改为行,行改为磅方法演示
C#中的方括号[](特性、属性)
首先要说的是,可能一些刚接触C#的朋友常常容易把属性(Property)跟特性(Attribute)弄混淆,其实这是两种不同的东西。属性就是面向对象思想里所说的封装在类里面的数据字段,其形式为:
vv彭
2020/12/28
7.8K0
C#中的方括号[](特性、属性)
C#中的类、方法和属性
这节讲C#中的类,方法,属性。这是面向对象编程中,我们最直接打交道的三个结构。
宿春磊Charles
2022/03/29
2K0
C#中的类、方法和属性
Mac复制改为拷贝
在默认情况下,mac电脑的复制为复制+粘贴,即会生成一个副本文件,并没有达到Win下复制功效。
努力在北京混出人样
2021/09/10
7460
C++17常用新特性(七)---新的属性和属性特性
C++17 增加了一些新的属性,这些属性并不是强制使用,但是正确使用后确实能够帮助我们避免一些问题,而这些问题恰恰是在做项目的时候容易忽略的,比较常见的一类问题是在前面把变量全部进行了定义,但是后面没有使用,还有一种是对于函数的返回值没有进行判断等,在本文中,将主要对C++17新增的一些属性进行解释和说明。
CPP开发前沿
2022/04/13
1.5K0
C++17常用新特性(七)---新的属性和属性特性
C#设置有命令空间的属性
之前被问到一个问题,C#中如何设置android:name这样的属性?我的第一反应是直接setAttribute不就可以了么 SetAttribute(name, value), 可事实上却不行,因为本身并没有什么C#的开发经验,周围也没什么人搞这个,所以就只能通过搜索了。
meteoric
2018/11/19
6960
[读书笔记]C#学习笔记五: C#3.0自动属性,匿名属性及扩展方法
前言 这一章算是看这本书最大的收获了, Lambda表达式让人用着屡试不爽, C#3.0可谓颠覆了我们的代码编写风格. 因为Lambda所需篇幅挺大, 所以先总结C#3.0智能编译器给我们带来的诸多好处, 下一遍会单独介绍Lambda表达式. 这篇主要包括的内容有: 自动属性,隐式类型,对象集合初始化,匿名类型,扩展方法. 下面一起来看下C#3.0 所带来的变化吧. 1,自动实现的属性 在C#3.0之前, 定义属性时一般会像下面这样去编写代码: 1 class Person 2 { 3 /
一枝花算不算浪漫
2018/05/18
8270
C# winform DataGridView 常见属性
C# winform DataGridView 属性说明 ① 取得或者修改当前单元格的内容 ② 设定单元格只读 ③ 不显示最下面的新行 ④ 判断新增行 ⑤ 行的用户删除操作的自定义 ⑥ 行、列的隐藏和删除 ⑦ 禁止列或者行的Resize ⑧ 列宽和行高以及列头的高度和行头的宽度的自动调整 ⑨ 冻结列或行 ⑩ 列顺序的调整 ⑪ 行头列头的单元格 ⑫ 剪切板的操作 ⑬ 单元格的ToolTip的设置 ⑭ 右键菜单(ContextMenuStrip)的设置 ⑮ 单元格的边框、 网格线样式的设定 ⑯ 单元格表示值的设定 ⑰ 用户输入时,单元格输入值的设定 ⑱ 设定新加行的默认值
全栈程序员站长
2022/09/07
3.9K0
C# 可空引用类型 Nullable 更强制的约束:将警告改为错误 WarningsAsErrors
于是 C# 8.0 带来的可空引用类型由于默认以警告的形式出现,所以实际上约束力非常弱。
walterlv
2023/10/22
4040
C# 可空引用类型 Nullable 更强制的约束:将警告改为错误 WarningsAsErrors
C++0x 通用属性
C++在不断的发展,但每一阶段的C++标准提供的功能都很难完全满足现实需求,于是为了弥补标准的不足或者扩增特性应用场景所需的特性,各大C++编译器厂商多多少少在标准之外都增加了不少有用的扩展功能。这些扩展功能并不在C++的标准中,但是却经常被使用。有时候,C++标准委员会也会考虑这些标准之外的扩增特性,将其纳入标准之中。
恋喵大鲤鱼
2018/09/27
9140
C# devExpress BandedGridView属性 备忘
BandedGridView属性备忘 StringBuilder sb = new StringBuilder(); DevExpress.XtraGrid.Views.BandedGrid.BandedGridView view = bandedGridView1 as DevExpress.XtraGrid.Views.BandedGrid.BandedGridView; List<GridBand> gridBand = new List<GridBan
乔达摩@嘿
2020/09/11
1.3K0
Android系统属性(c接口)
属性是在整个系统中全局可见的。每个进程可以get/set属性。在编译的过程中会将各种系统参数汇总到build.proc 以及default.proc 这两个文件中,主要属性集中在build.proc中。
李小白是一只喵
2020/04/24
9500
Android系统属性(c接口)

相似问题

Wix ProgramFiles64Folder仍然

40

ConnectionString属性尚未初始化。怎么修呢?

22

将属性更改为最终属性

11

Eclipse -属性改为大写

10

WPF / C#将ObservableCollection中的项的属性更改为ListBox

36
添加站长 进交流群

领取专属 10元无门槛券

AI混元助手 在线答疑

扫码加入开发者社群
关注 腾讯云开发者公众号

洞察 腾讯核心技术

剖析业界实践案例

扫码关注腾讯云开发者公众号
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文