Подтвердить что ты не робот

Почему char [] и char * как typedefs разные, но иногда... нет?

После наблюдения этого вопроса возникло следующее замечание о различиях char[] и char*.

#include <iostream>

typedef char ar[];
typedef char* pr;
void f2(ar x, pr y)
{
    std::cout << std::is_same<decltype(x), decltype(y)>::value << '\n';
    std::cout << std::is_same<ar, pr>::value << '\n';
}

int main()
{
    char data[] = "data";
    char *ptr = data;
    f2(data,ptr);
    return 0;
}

Вывод (на Apple LLVM версии 4.2 (clang-425.0.28))

1
0

Почему данные относятся к разным типам, но не отличаются от decltype()? Мое подозрение в том, что они действительно разные типы из-за их объявлений typedef, но тогда почему переменные сообщаются как один и тот же тип?

4b9b3361

Ответ 1

В С++, как и в C, параметр, объявленный как тип массива, настраивается (во время компиляции) на тип указателя, в частности указатель на тип элемента массива.

Это происходит независимо от того, указан ли тип массива напрямую или через typedef (помните, что typedef не создает новый тип, просто псевдоним для существующего типа).

Итак, это:

typedef char ar[];
typedef char* pr;
void f2(ar x, pr y)
{
    // ...
}

действительно означает:

void f2(char* x, char* y)
{
    // ...
}

Другое правило, также разделяемое C и С++, заключается в том, что выражение типа массива в большинстве, но не во всех контекстах, неявно преобразуется в указатель на первый элемент объекта массива. Это означает, что если вы определяете объект массива:

char arr[10];

вы можете использовать имя этого объекта в качестве аргумента функции, которая принимает параметр char* (который теряет информацию о границах).

В C случаи, когда это неявное преобразование не происходит, следующие:

  • Когда выражение массива является операндом sizeof (sizeof arr дает размер массива, а не размер указателя);
  • Когда выражение массива является операндом унарного & (&arr является указателем на массив, а не указателем на указатель); и
  • Когда выражение массива является строковым литералом, используемым для инициализации объекта типа массива (char s[] = "hello"; инициализирует s как массив, а не как указатель).

Ни один из этих случаев (или другие случаи, которые происходят на С++) не отображаются в вашей программе, поэтому ваш вызов:

f2(data,ptr);

передает два значения указателя типа char* в f2.

Внутри f2 объекты параметров x и y являются типами char*, поэтому std::is_same<decltype(x), decltype(y)>::value является истинным.

Но типы ar и pr различны. ar - это неполный тип массива char[], а pr - тип указателя char*.

Что объясняет вывод вашей программы. Странность возникает, потому что параметр x, который вы определили с типом массива ar, действительно имеет тип char*, который является тем же самым типом, что и pr.

Ответ 2

Семейство C является передачей по значению, а значение C массива является указателем на его первый элемент. Когда вы передаете элемент, объявленный как массив для функции, то, что действительно получается, является указателем, а C обрабатывает прототип, как если бы вы объявили его таким образом.

Ответ 3

Я изменил код, чтобы мы могли видеть, как вызывает изменение типа f2. Перед вызовом переменные имеют разный тип. После вызова они стали такими же

    typedef char ar[];
typedef char* pr;
void f2(ar x, pr y)
{
    cout << is_same<decltype(x), decltype(y)>::value << '\n'; //same type
}

int main()
{
    ar data = "data";
    pr ptr = data;
    cout << is_same<decltype(data), decltype(ptr)>::value << '\n'; // different
    f2(data,ptr);
    return 0;
}

выход 0 0 . Как @jthill, @Dyp и @keith Thompson говорят, что это происходит из-за разложения массива на указатель.