【问题标题】:Validity of the code代码的有效性
【发布时间】:2010-01-10 04:41:23
【问题描述】:

考虑以下代码:

void populate(int *arr)
{
   for(int j=0;j<4;++j)
       arr[j]=0;
}

int main()
{
   int array[2][2];
   populate(&array[0][0]);
}

在当地社区对此代码是否有效进行了讨论(我应该提及它的名称吗?)。一个人说它调用了UB,因为它违反了

C++ 标准($5.7/5 [expr.add])

"如果指针操作数和结果都指向同一个数组对象的元素,或者超过数组对象的最后一个元素,则计算不应产生溢出;否则,行为未定义。”

但我看不出代码有什么问题,代码对我来说完全没问题。

所以,我只想知道这段代码是否有效?我错过了什么吗?

【问题讨论】:

    标签: c++ undefined-behavior


    【解决方案1】:

    您的arrayint[2] 的两个数组,而您的函数populate() 将其视为int[4] 的单个数组。根据编译器决定对齐array 元素的确切方式,这可能不是一个有效的假设。

    具体来说,当j 为2 并且您尝试访问arr[2] 时,这超出了mainarray[0] 的范围,因此无效。

    【讨论】:

    • 但是当你访问arr[2]时,arr不是一个“任何东西的数组[2]”,它是一个指针。我不确定这个论点是否适用。
    • @Alok:在 C++ 中,多维数组与指针数组不同。 Java只有后者,而C和C++两种形式都有。
    • @Greg/Alok:我不确定我是否理解“取决于编译器如何决定对齐数组元素”的意思。对齐与此有什么关系?如果我理解 Alok 的回答(如果不理解,请纠正我),符合标准的编译器 必须 以特定方式为此定义分配内存。未定义的行为不是来自编译器如何选择布局内存,而是来自稍后进行的无效索引。
    • @Edan:是的。数组元素将是连续的。未定义的行为来自无效的索引。正如我在帖子中所说,&amp;array[0][0]+2 == &amp;array[1][0],但(&amp;a[0][0] + 2) + 1 未定义,而&amp;a[1][0] + 1 有效。
    • @Emile:是的,标准并不关心指针是如何实现的,只要它的行为符合预期即可。指针不是内存地址。以 Flashbytecode 为目标的编译器为例。该环境中不存在原始指针,因此必须将指针编译为...其他一些表示形式。指针是一个 C/C++ 概念。仅仅因为它通常被编译成一个内存地址,并不意味着它必须是等价的。
    【解决方案2】:

    你有一个数组数组。 array[1] 在内存中跟在 array[0] 后面,因为数组是连续的。如果p == array[0],则p[1] 跟随p[0],因为数组是连续的。所以,你是对的:array 的所有内存都是连续的。

    在图片中,array 看起来像这样。

    +-----------------+-----------------+
    |      [0]        |      [1]        |
    +-----------------+-----------------+
    

    现在,让我们分解array[0]array[1],它们分别看起来像这样:

    +--------+--------+
    |  [0]   |  [1]   |        
    +--------+--------+
    

    所以,最终的图片是:

    +--------+--------+--------+--------+
    | [0][0] | [0][1] | [1][0] | [1][1] |
    +--------+--------+--------+--------+
    

    现在,问题是,您能否以您的方式访问这个连续的内存。答案是,标准不保证。数组是连续的,但标准不允许按照您所做的方式进行索引。换句话说:

    &amp;array[0][0]+2 == &amp;array[1][0],但(&amp;a[0][0] + 2) + 1 未定义,而&amp;a[1][0] + 1 有效。如果这看起来很奇怪,那就是,但是根据您从标准中发布的报价,您只能计算一个位于数组内部或最多超过数组的指针(不取消引用“过去”指针) .

    实际上,我怀疑这会在任何地方失败,但至少根据标准,您的代码由于未定义的行为而无效。

    参见this post on comp.lang.c

    【讨论】:

    • 第三张图的最后一个元素不应该是“[1][1]”吗?
    • 问题不在于内存是否连续(显然是),而在于行为是否定义明确。我不明白这怎么可能是“对的”。他问是不是UB,答案是肯定的。它通常会起作用的事实并没有使它变得不那么不确定。
    • jalf,你是对的。当我说“半对”时,我的意思是有些人认为内存不必是连续的,因此行为是未定义的。另一方面,许多其他人认为记忆必须是连续的,因此行为是明确定义的。
    • @vobject:已修复。 @jalf:我改变了答案的措辞。
    • 还需要添加到混合中的是 CPU 配置。例如,DEC 内存模型与 PC 内存模型相反。因此,数组也会被反转,并且作为单个数组访问它们会有所不同。
    【解决方案3】:

    这并不总是有效。 C 有数组数组,而不是二维数组。子数组并不总是指定在内存中是连续的(静态数组可能是,检查 C/C++ 标准)在这个特定的例子中,我怀疑它可以正常工作。但是,如果您动态分配了传入的内存,您很可能会失败,因为 malloc(或 new)可能会将子数组相距很远。

    但是,如果您想线性地遍历“二维”内存,则可以针对一维数组构造一个二维访问器,它可以正常工作,而 memset 之类的东西也可以针对一维数组工作。

    【讨论】:

    • 数组需要有连续的内存(ISO C99 标准中的第 6.2.5 节,第 10 条)。如果多维数组被定义为数组的数组,那么这是否意味着它们也是连续的(第 n+1 个数组必须紧跟在内存中的第 n 个数组之后)?
    • 它们必须是连续的,但它们之间可能存在实现定义的填充。不过,在我使用过的任何编译器上,我都不知道这对int 来说是个问题。
    • 啊,好点子。填充可能是多维 char 数组的问题。
    • 填充在哪里?在array[i] 的最后一个元素和array[i+1] 的第一个元素之间?但是array[i]array[i+1] 是数组的连续元素,所以不能有任何填充。
    • malloc 返回一块连续的内存。将 malloc 的结果视为 int 数组的数组永远不会导致“子数组相距甚远”。
    【解决方案4】:

    C 中,所有内容都存储在线性内存段中。您正在传递a[0][0] 的地址,这与a[0] 的地址相同,所以 a[i][j]a[i*ColSize+j] 相同,因为所有内容都是线性存储的。但是,如果您动态分配内存,它将失败,因为那时所有行可能都不会存储在连续的位置。那么a[i][j] 将是*(&amp;a[i]+j)

    【讨论】:

    • 否 - 数组不需要按标准线性存储。如果它被声明为一个二维数组,它就是一个二维数组——不是一个大到足以容纳所有子数组的单个数组。
    • BillyONeal,数组数组仍然是数组,其元素是连续存储的。
    猜你喜欢
    • 1970-01-01
    • 2010-10-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-10-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多