我有这样一个代码:
#include <stdlib.h>
#include <stdio.h>
void func(int **b)
{
printf("b = %p\n", b); // 0x7ffe76932330
*b = *b + 1;
}
int main(void)
{
int b[10] = {0};
printf("b = %p\n", &b[0]); // 0x7ffe76932330
printf("%d\n", b[0]); // 0
func(&b);
printf("%d\n", b[0]); // 4
return 0;
}
字符串
这个代码有UB吗?对我来说似乎是这样,至少是由于不同的类型没有显式的转换int (*)[10] != int **
。
如果我有char b[] = "some string";
呢?行为几乎是一样的。。奇怪
3条答案
按热度按时间ktca8awb1#
传递指针本身不一定是未定义行为,但随后使用转换后的指针是未定义行为。
C允许从一种对象类型转换到另一种对象类型,如C standard的第6.2.3.2p7节所述:
指向对象类型的指针可以被转换为指向不同对象类型的指针。如果结果指针没有与引用的类型正确对齐,则行为未定义。否则,当再次转换回来时,结果应与原始指针相等。当指向对象的指针转换为指向字符类型的指针时,结果指向对象的最低寻址字节。结果的连续递增(直到对象的大小)将产生指向对象剩余字节的指针。
因此,假设不存在对准问题(即,数组在64位系统上从8字节偏移量开始),只允许将
int (*)[10]
传递给期望int **
的函数,尽管大多数编译器会警告转换不兼容的指针类型。未定义的行为发生在这里:
字符串
因为你通过一个不兼容的指针类型(除了
char *
)派生一个对象。关于允许取消引用的规则在第6.5p7节中列出:对象的储存值只能由具有下列其中一种型别的左值运算式存取:
将
int (*)[10]
解引用为int **
不满足上述任何条件,因此*b
是未定义行为。h79rfbju2#
一个数组不是一个指针,因此当你用
func(&b)
传递给func
时,一个指向数组的指针不是一个指向指针的指针。它是一个指向数组的指针,这是一种不常见的类型,在将数组的数组传递给函数时会产生(int b[10][10]
定义了int
的数组的数组)。传递
&b
到func
涉及到指针类型之间的转换,这是C标准允许的,但程序员应该小心:如果配置正确,编译器将发出警告:gcc和clang推荐使用-Wall -Werror
。关于未定义的行为本身:将
&b
传递给func
,并期望得到int **
。编译器执行从&b
类型(int(*)[10]
)到int **
类型(可能具有不同的对齐要求)的转换。实际上,b
的对齐宽度为int
(通常为4个字节),而int *
可能需要8个字节的对齐,大多数64位系统都是如此。C23标准将此转换指定为具有未定义的行为:
6.3.2.3指针:
指向对象类型的指针可以被转换为指向不同对象类型的指针。如果结果指针没有与引用的类型正确对齐,则行为未定义。
因此,标准将这种转换描述为具有未定义的行为。
如果
int *
和int
具有相同的对齐要求,例如在32位系统上,将&b
传递给func
时不会出现未定义的行为,但在计算表达式*b = *b + 1;
时会出现未定义的行为,因为:6.5表达式
对象的储存值只能由具有下列其中一种型别的左值运算式存取:
因此,在
*b = *b + 1
中取消引用b
具有未定义的行为。例如,您可以尝试调用func(&(b+1))
来检查未定义的行为是否更明显(程序可能会因总线错误而退出)。还要注意,
printf
需要void *
作为%p
,因此b
和&b[0]
必须转换为(void *)
,以避免另外两个未定义行为的示例。yhxst69z3#
表达式
&b
的类型是int ( * )[10]
。从int ( * )[10]
类型的指针到int **
类型的指针没有隐式转换。所以编译器应该为这个语句发出一条消息字符串
但是,即使你将参数表达式转换为:
型
然而,解引用所获得的指针表达式可能会调用未定义的行为。也就是说,函数调用中使用的表达式
&b
与数组第一个元素的地址值相同。因此,在函数中,表达式
*b
产生第一个元素的值(如果sizeof( int * )
等于sizeof( int )
,例如当两者都等于4
时)或所传递数组的两个前元素的组合值(如果sizeof( int * )
等于2 * sizeof( int )
,例如指针的大小等于8
,整数的大小等于4
)。也就是说,表达式
*b
将不包含有效地址。因此,这一声明:
型
没有道理。在所提供的示例中,当初始数组被零初始化时,表达式
*b
可以产生空指针。您可以在函数中测试表达式,例如如下所示型
字符数组也会出现同样的问题:
型
如果你用同样的方式。
你可以这样写,例如:
型
在这种情况下,函数中的表达式
*b
将指向传递的数组的第一个元素和以下语句:型
将递增所获得的指针,该指针现在将指向数组的第二个元素。