具有非常量指针和相同地址的const参数的指针的函数调用


14

我想编写一个函数,该函数输入一个数据数组并使用指针输出另一个数据数组。

我想知道如果两者都指向同一地址会产生什么结果srcdst因为我知道编译器可以针对const进行优化。它是未定义的行为吗?(我同时标记了C和C ++,因为我不确定答案是否可能会有所不同,并且我想了解两者。)

void f(const char *src, char *dst) {
    dst[2] = src[0];
    dst[1] = src[1];
    dst[0] = src[2];
}

int main() {
    char s[] = "123";
    f(s,s);
    printf("%s\n", s);
    return 0;
}

除了上述问题,如果删除const原始代码中的,是否定义明确?

Answers:


17

尽管行为的定义是正确的,但编译器可以按照您的意思“优化const” 并不是正确的。

也就是说,编译器容许假设,仅仅因为一个参数是const T* ptr,内存指向ptr不会通过另一个指针被改变。指针甚至不必相等。这const是一种义务,而不是保证-您(=函数)的一项义务是不要通过该指针进行更改。

为了真正获得保证,您需要使用restrict关键字标记指针。因此,如果您编译这两个函数:

int foo(const int* x, int* y) {
    int result = *x;
    (*y)++;
    return result + *x;
}

int bar(const int* x, int* restrict y) {
    int result = *x;
    (*y)++;
    return result + *x;
}

foo()函数必须从中读取两次x,而bar()只需要读取一次:

foo:
        mov     eax, DWORD PTR [rdi]
        add     DWORD PTR [rsi], 1
        add     eax, DWORD PTR [rdi]  # second read
        ret
bar:
        mov     eax, DWORD PTR [rdi]
        add     DWORD PTR [rsi], 1
        add     eax, eax              # no second read
        ret

看到这个直播GodBolt

restrict只是C中的关键字(自C99起);不幸的是,到目前为止,尚未将其引入C ++(由于可怜的原因,将其引入C ++更为复杂)。但是,许多编译器确实支持它__restrict

底线:编译器在编译时必须支持您的“神秘”用例f(),并且不会有任何问题。


有关的用例,请参阅此帖子restrict


const不是“您(=函数)没有通过该指针进行更改的义务”。C标准允许该函数const通过强制转换删除,然后通过结果修改对象。本质上,const这只是建议性的提示,为程序员提供了便利,可以帮助避免意外修改对象。
埃里克·Postpischil

@EricPostpischil:这是您可以摆脱的义务。
einpoklum

您可以摆脱的义务不是义务。
Eric Postpischil

2
@EricPostpischil:1.您在这里劈头发。2.那不是真的。
einpoklum

1
这就是为什么memcpystrcpy使用restrict参数声明的原因,而memmove并非如此-仅有后者允许内存块之间有重叠。
Barmar

5

这是明确定义的(使用C ++,再也不确定使用C),带有或不带有const限定符。

要寻找的第一件事是严格的别名规则1。如果srcdst指向同一对象:

  • 在C中,它们必须是兼容类型char*并且char const*不兼容。
  • 在C ++中,它们必须是相似的类型char*char const*相似。

关于const限定符,您可能会争辩说,由于当dst == src函数有效修改src指向的内容时,src不应将其限定为const。这不是const工作方式。需要考虑两种情况:

  1. 当将一个对象定义为时(const如中所述)char const data[42];,(直接或间接地)对其进行修改将导致未定义的行为。
  2. const定义了对象的引用或指针时(如中所述)char const* pdata = data;,只要未将其定义为const2(请参阅1.),就可以修改基础对象。因此,以下是明确定义的:
int main()
{
    int result = 42;
    int const* presult = &result;
    *const_cast<int*>(presult) = 0;
    return *presult; // 0
}

1) 什么是严格的别名规则?
2) const_cast安全的吗?


也许OP意味着可能重新分配作业?
Igor R.

char*并且char const*不兼容。_Generic((char *) 0, const char *: 1, default: 0))计算为零。
Eric Postpischil

const定义对象的引用或指针时”的措词不正确。您的意思是,定义了const-qualified 类型的引用或指针时,这并不意味着它所指向的对象可能不会被修改(通过各种方式)。(如果指针确实指向一个const对象,则意味着该对象确实是const按定义定义的,因此未定义尝试对其进行修改的行为。)
Eric Postpischil

@Eric,仅在有关标准或标记的问题时才具体说明language-lawyer。我很珍惜价值,但我也知道它带来了更多的复杂性。在这里,我决定使用简单且易于理解的句子,因为我相信这是OP想要的。如果您认为没有其他问题,请回答。我将是第一个对此提出支持的人。无论如何,谢谢您的评论。
YSC

3

这在C语言中定义良好。严格的别名规则不适用于该char类型,也不适用于相同类型的两个指针。

我不确定“优化const” 是什么意思。我的编译器(GCC 8.3.0 x86-64)在两种情况下都生成完全相同的代码。如果将说明restrict符添加到指针,则生成的代码会稍微好一些,但是对于您的情况,这是不可行的,因为指针是相同的。

(C11§6.57)

一个对象只能通过具有以下类型之一的左值表达式访问其存储值:
—与该对象的有效类型兼容的类型,
— 与该对象的有效类型兼容的类型的限定版本,
—类型是与对象的有效类型相对应的有符号或无符号类型,
- 与对象的有效类型的合格版本相对应的有符号或无符号类型的类型,
-包括一个在其成员之间(包括递归地包括一个子集合或所包含的并集的成员)中的上述类型,或
-字符类型。

在这种情况下(没有restrict),您将始终得到121结果。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.