Вызов функции с указателем на неконстантный и указатель на константные аргументы с тем же адресом - PullRequest
14 голосов
/ 13 марта 2020

Я хочу написать функцию, которая вводит массив данных и выводит другой массив данных с использованием указателей.

Мне интересно, каков результат, если оба параметра src и dst указывают на тот же адрес, потому что я знаю, что компилятор может оптимизировать для 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 в исходном коде?

Ответы [ 3 ]

17 голосов
/ 13 марта 2020

Хотя верно, что поведение четко определено - это не верно, что компиляторы могут "оптимизировать для 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.

Итог: компилятор должен поддерживать ваш вариант использования "esoteri c" при компиляции f() и не будет иметь никаких проблем с it.


См. этот пост относительно вариантов использования для restrict.

5 голосов
/ 13 марта 2020

Это четко определено (в C ++, больше не уверен в C), с квалификатором const и без него.

Первое, на что нужно обратить внимание, это правило строгого наложения имен 1 . Если src и dst указывают на один и тот же объект:

Относительно квалификатора const, вы можете утверждать, что с тех пор, как dst == src ваша функция эффективно модифицирует то, на что указывает src, src следует не может быть квалифицирован как const. Это не то, как const работает. Необходимо рассмотреть два случая:

  1. Когда объект определен как const, как в char const data[42];, его изменение (прямо или косвенно) приводит к неопределенному поведению.
  2. Когда определяется ссылка или указатель на объект const, как в char const* pdata = data;, можно изменить базовый объект, если он не был определен как const 2 (см. 1.). Таким образом, следующее четко определено:
int main()
{
    int result = 42;
    int const* presult = &result;
    *const_cast<int*>(presult) = 0;
    return *presult; // 0
}

1) Что такое правило строгого наложения имен?
2) Безопасно ли const_cast?

3 голосов
/ 13 марта 2020

Это четко определено в C. Строгие правила псевдонимов не применяются ни к типу char, ни к двум указателям одного типа.

Я не уверен, что вы имеете в виду под "оптимизацией для const". Мой компилятор (G CC 8.3.0 x86-64) генерирует одинаковый код для обоих случаев. Если вы добавите спецификатор restrict к указателям, то сгенерированный код будет немного лучше, но это не сработает для вашего случая, указатели будут такими же.

(C11 §6.5 7)

Объект должен иметь свое сохраненное значение, доступное только через выражение lvalue, имеющее один из следующих типов:
- тип, совместимый с действующим типом объекта,
- квалифицированная версия типа, совместимого с действующим типом объекта,
- тип, который является типом со знаком или без знака, соответствующим действительному типу объекта,
- тип, который является типом со знаком или без знака, соответствующим квалифицированная версия действующего типа объекта,
- агрегатный или объединенный тип, который включает в себя один из вышеупомянутых типов среди своих членов (включая, рекурсивно, член субагрегированного или автономного объединения), или
- a тип персонажа.

В этом случае (без restrict) в результате вы всегда получите 121.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...