由 C++ 编译器优化时,F2C 翻译的代码中断

Posted

技术标签:

【中文标题】由 C++ 编译器优化时,F2C 翻译的代码中断【英文标题】:F2C translated code breaks when optimized by C++ compiler 【发布时间】:2012-06-05 10:37:15 【问题描述】:

我有一个 c++ 程序,其方法如下所示:

int myMethod(int* arr1, int* arr2, int* index)

    arr1--;        
    arr2--;
    int val = arr1[*index];
    int val2 = arr2[val];
    return doMoreThings(val);

启用优化 (/O2) 后,第一个指针递减的第一行不会被执行。我正在并排调试优化和非优化构建,优化构建逐步减少,而非优化程序执行它。这会在稍后使用 arr[*index] 访问数组时产生明显的行为差异。

更新

正如@stefaanv 所指出的,减量确实可能被编译器忽略,如果它改为减量的访问索引,它似乎会这样做。因此,省略的减量并不是导致行为差异的原因。相反,导致它的矩阵的使用存在某些问题。

进一步看,我已将其缩小为包含执行矩阵乘法的嵌套循环的方法。该方法的一部分如下所示:涉及 3 个数组:a、wa 和 t。在该方法的开始,f2c 翻译器使用递减,以便在 fortran 中为 6 x 6 的数组在 c 中是平坦的double[36]。但是为了能够使用旧的索引,它将数组指针向后移动矩阵中的列数。

通常在这个 f2c 翻译的程序中,平面数组作为 &someArray[1] 传递,方法从每个数组减一开始。 @Christoph 指出这应该是有效的,因为数组的减量永远不会超出其声明的范围。

在这种方法的情况下,传入的数组不会作为指向数组&someArray[1] 中更远的元素的指针传递,但这里的数组是声明为固定大小的本地静态数组,例如mat[36] 并直接传递给乘法方法。

void test()

    double mat[36];
    ...
    mul(mat, .., ..)


void mul(double* a, double* t, double*wa, int M, int N, int K)

    // F2C array decrements.
    a -= (1+M); // E.g. decrement by seven for a[6x6]!
    t -= (1+N); 
    wa--;
    ...
    for (j = K; j <= M; ++j)          
       for (i = 1; i <= N; ++i) 
          ii = K;
          wa[i] = 0.;          
          for (p = 1; p <= N; ++p) 
             wa[i] += t[p + i * t_dim1] * a[ii + j * a_dim1];
             ++ii;
          
       

       ii = K;      
       for (i = 1; i <= N; ++i) 
          a[ii + j * a_dim1] = wa[i];
          if (j > kn) 
             a[j + ii * a_dim1] = wa[i];
          
          ++ii;
      
    
     

所以问题是:

这是否意味着当您执行 f2c 在此处所做的操作时,行为未定义并且可能会在优化下中断,即从 double[36] 数组指针中减去 7,然后在正确的位置访问数组中的所有项目(偏移量 7)?

编辑:在C FAQ 中找到这个,这适用于这里吗?

指针算术仅在指针指向时定义 在同一个分配的内存块内,或者到虚构的 “终止”元素过去;否则,行为是 未定义,即使指针未被取消引用。 …… 参考文献:K&R2 Sec。 5.3 页。 100,秒。 5.4 第 102-3 页,秒。 A7.7 第 205-6 页; ISO 秒。 6.3.6;理由二。 3.2.2.3.

更新 2:

如果我使用递减索引而不是递减指针重新编译多维数组,

#define a_ref(a_1,a_2) a[(a_2)*a_dim1 + a_1 - 1 - a_dim1]

a_ref(1,2);

然后,无论优化如何,该方法都会产生相同的(预期的)输出。仅减一的一维数组似乎不会产生任何问题。

我可以将程序中的所有多维数组都更改为使用上述访问方法,但是单个dim数组太多无法手动更改,因此理想情况下我想要一个适用于两者的解决方案。

新问题:

f2c 是否可以选择使用这种数组访问方法而不是摆弄指针?似乎这将是 f2c 中的一个简单更改,并生成定义良好的代码,因此您会认为这已经是一种选择。 对于这个问题还有其他解决方案吗(除了跳过优化并希望程序表现良好,尽管依赖于未定义的行为)。 我可以在 c++ 编译器中做些什么吗?我使用 Microsoft C++ (2010) 作为托管 c++ 项目进行编译。

【问题讨论】:

你的意思是arr2--没有被执行,不是吗? 您不是说代码是否会改变行为并中断,这就是您要问这个的原因吗?您或许应该包括汇编输出。 我将添加更多上下文/说明。 @Greg:不,不执行的减量是arr1数组的减量。 @Anders Forsgren:显示整个方法的组装(有和没有 O2)。 【参考方案1】:

优化器应该只确保没有可观察到的行为变化,因此它可以选择不进行递减并使用递减索引访问数据(额外的偏移量可以是操作码的一部分),因为函数使用指向数组的指针的副本。你没有告诉我们数组实际上是如何被访问的,优化器是否真的引入了错误,所以我只能猜测这个。

但正如 slartibartfast 已经说过的:这是未定义的行为,在检查 *index > 0 后,减量应替换为 int val = arr1[*index-1];

【讨论】:

它跳过递减并且不修改访问器逻辑,因此可观察到的行为变化。如果数组减量始终未定义,我可能需要找到一个 f2c 参数,它会在访问时生成减量索引。即使按照我用 &a[1] 描述的方式调用该方法,它是否也是未定义的行为? (见我的最后一次编辑)。编辑:仔细观察它似乎确实尝试使用递减索引访问数组的反汇编。我将研究为什么这不会产生预期的输出 @Anders:只有当指针离开指向数组的边界时,减量才是未定义的;如果你通过&amp;a[1] 并且只递减一次,则行为是完全明确的 @Christoph:这是基于标准还是“大多数编译器实现”? @Christoph:请忽略这个问题,我刚刚看到你对 slartibartfast 的回答的评论。【参考方案2】:

将指向数组的指针移出数组指定范围,然后通过它进行访问(尽管完整的表达式回落到该范围内)是未定义的行为 AFAIK。尽管如此,在几乎每个实现中,这段代码都应该像预期的那样工作,所以问题是,你在看什么?也许在从 arr1[] 获取 int 的汇编指令中有一个隐式的预减量?仅仅因为您在调试器中看不到减量并不意味着它不存在。通过向其写入区分值来检查是否访问了正确的元素。

【讨论】:

不通过访问也是UB。 您声称如果pint*,那么(p-1)+n 并不总是与p+(n-1) 相同,对吗?如果是,你确定吗? @TonyK: 如果p 指向int[] 的第一个元素,那么表达式p-1 会根据C99/C11 6.5.6 §8 调用未定义的行为,而您不能关于(p-1)+n的原因;在没有边界检查的实现中,它可能等于p+(n-1),但这并不能由语言语义保证,只是作为指针实现方式的副作用而发生 @Christoph:是的,好的。 (但在最新的 C++11 工作草案 N3337 中,它是 5.5.7 §5。) 如果它是规范中的UB,有没有办法找出我的编译器对它的看法?即,是否有关于 msvc 如何处理这些情况的描述?

以上是关于由 C++ 编译器优化时,F2C 翻译的代码中断的主要内容,如果未能解决你的问题,请参考以下文章

C++中的volatile是啥意思?

在 f2c.h 文件中定义 min() max() 宏时出错

与头文件一起使用时,使用个人 C++ 库编译代码会中断

如何将javascript代码编译为c++或java

JavaScript工作机制:V8 引擎内部机制及如何编写优化代码的5个诀窍

C代码编译为C++,但不是C [重复]