Почему 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()s ? Я подозреваю, что они на самом деле разные типы из-за их typedef объявлений, но тогда почему переменные сообщаются как тот же самый тип?

3 13

3 ответа:

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

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

Так вот:

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

На самом деле означает:

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

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

char arr[10];

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

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

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

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

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.

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

Я изменил код, чтобы мы могли видеть, как вызов 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;
}

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