将数组强制转换为char*是否意味着对字符串';长度是多少?

将数组强制转换为char*是否意味着对字符串';长度是多少?,c,undefined-behavior,C,Undefined Behavior,这个代码应该打印什么 #include <stdio.h> #include <string.h> struct S { int x[1]; }; union U { struct S arr[64]; char s[256]; }; int main() { union U u; strcpy(u.s, "abcdefghijklmnopqrstuvwxyz"); size_t len = strlen((char*

这个代码应该打印什么

#include <stdio.h>
#include <string.h>

struct S
{
    int x[1];
};

union U
{
    struct S arr[64];
    char s[256];
};

int main()
{
    union U u;
    strcpy(u.s, "abcdefghijklmnopqrstuvwxyz");
    size_t len = strlen((char*)&u.arr[1].x);
    puts(len > 10 ? "YES" : "NO");
    return 0;
}
#包括
#包括
结构
{
int x[1];
};
联合大学
{
结构S arr[64];
chars[256];
};
int main()
{
欧盟;
strcpy(美国,abcdefghijklmnopqrstuvwxyz);
size_t len=strlen((char*)&u.arr[1].x);
放置(长度>10?“是”:“否”);
返回0;
}

叮当声总是打印“是”。GCC 8.1打印带有优化的“否”,但不发出警告。它是否利用了一些未定义的行为?

是的,
gcc 8.1
正在利用未定义的行为。调用strlen时,您对大小为1的数组具有越界访问权限

strlen((char*)&u.arr[1].x);

&u.arr[1].x的类型是
int(*)[1]
。然后您将其强制转换为
char*
。除非用作
sizeof
的操作数,否则数组的地址与第一个元素的地址具有相同的值。因此,在强制转换之前,它的值为
&u.arr[1].x[0]
,类型为
int[1]
。假设
sizeof(int)==4
,您可以看到读取超过4个字节会导致越界访问


大小为1的数组之后是否有有效内存并不重要。如果使用较小的基指针派生指针并从中读取,则行为未定义

通过将数组大小更改为1、2和3,并从
gcc
检查生成的程序集,可以确认这是确切的原因

对于1和2,它生成
put(“NO”)
。但对于3,它会生成预期的代码。 这是因为你正在与10进行比较。使用
int[2]
,长度永远不能大于10(不调用UB)。但3的最大字节数是12

您可以在此处看到生成的程序集-

vs


您可能还想查看我的老问题,以便对2D阵列进行类似的讨论

适合系统编程的实现将允许使用指向内部对象的指针来派生指向包含对象的指针。然而,C标准并未要求所有一致性实施适用于任何目的(作者在理论基础中承认,可以构建质量如此之低以至于基本无用的一致性实施),更不用说它们都适合系统编程了。另一方面,它描述了一种相当简单的方法,用于系统编程的实现可以通过这种方法提供必要的语义

特别是,虽然本标准没有规定从
T*
V*
的直接转换将作为从
T*
U*
的转换,然后是从
U*
V*
的转换,如果存在某种类型的
U*
支持往返于
T*
V*
的转换,那么这种行为在编写时肯定很常见。许多行为本不由标准定义的操作将在确保指针强制转换行为可传递的实现上定义

除此之外,该标准还规定,经过适当转换的指向聚合(数组、结构或联合)的指针将生成指向其第一个元素/成员的指针,反之亦然。因此,将&u.x[0]转换为
int(*)[1]
,将其转换为
struct S*
,然后转换为
union u*
,最后转换为
char*
,将产生一个
char*
,可用于索引整个结构。虽然标准可能允许一致性实现以仅允许访问其地址已转换的特定“内部”对象的方式处理转换为
char*
,但它并不意味着实现应该这样做,也不意味着这样的限制不会使实现不适合系统编程

PS——我当然可以看到范围限制限定符的好处,它指示指向特定对象的指针不会用于派生该对象之外的任何对象的地址。假设:

struct foo {int x,y,z; };
...
int test(struct foo restrict *it)
{
  it->y++;
  doSomething(&it->x);
  it->y--;
  return it->y;
}
在参数上存在这样一个限定符
doSomething()
将允许编译器优化
操作->y是否知道
doSomething()
的代码。然而,请注意,要想使这种限定符最有用,就需要像
restrict
一样,通常会清洗指针的操作不会擦除其效果。因此,在可能的范围内,将不合格的强制转换视为清洗指针比将强制转换视为产生范围有限的指针更有意义,除非明确清洗。

类型
&u.arr[1]。x
int(*)[1]
。将其强制转换为
char*
并复制更多的
sizeof(int)
是越界访问。大小
1
数组之后是否有有效内存并不重要。如果使用较小的基指针派生指针并从中读取,则行为未定义。此外,我会使用
u.arr[1].x
&u.arr[1].x[0]
进行测试。我想知道,如果将数组改为
int[3]
,而不是
int[1]
,它将直接停止打印
NO
,为什么要将
&u.arr[1].x
gcc 8.1
一起使用。因此,它肯定是在识别一个越界访问。因为
10<12
但是
10>8
。请参阅vs“我想知道为什么您使用
&u.arr[1].x
”来轻松调整
x
的类型/算术。但是,请注意6.3.2.3/7中的特殊规则,该规则允许使用字符指针在任何对象上迭代。因此,如果输入不是一个超出范围的数组,而是一个完整的结构或结构数组,我会期望一个不同的b