Имена массивов в аргументах функций обрабатываются иначе, чем массивы, объявленные локально (авто) - PullRequest
2 голосов
/ 25 мая 2019

Пожалуйста, прочитайте комментарии в программе ниже:

#include<stdio.h>
void test(char c[])
{
    c=c+2; //why does this work ?
    c--;
    printf("%c",*c);
}
int main()
{
    char ch[5]={'p','o','u','r'};
    //ch = ch+2;  //this is definitely not allowed on array names as they are not pointers
    test(ch);

    return 0;
}

OUTPUT
o

Ответы [ 4 ]

0 голосов
/ 25 мая 2019

В объявлениях параметров функции объявление массива автоматически корректируется, чтобы быть объявлением указателя, согласно C 2018 6.7.6.3 7:

Объявление параметра как «массива тип » должно быть скорректировано на «квалифицированный указатель на тип »,…

Таким образом void test(char c[]) эффективно void test(char *c).

В main, ch - это массив, поскольку он был объявлен с помощью char ch[5]…, что является обычным объявлением, которое не корректируется. В test, c - указатель.

Когда main вызывает test с test(ch), аргумент ch является выражением. В выражении массив в большинстве случаев автоматически преобразуется в указатель, потому что C 2018 6.3.2 3 говорит:

За исключением случаев, когда это операнд оператора sizeof или унарный оператор & или строковый литерал, используемый для инициализации массива, выражение имеет тип «массив из тип » »Преобразуется в выражение с типом« указатель на тип », которое указывает на начальный элемент объекта массива…

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

Обратите внимание, что настраивается только внешний массив. Если объявление параметра функции int x[3][4], оно устанавливается на int (*x)[4], указатель на массив 4 int. Только массив, который является параметром (массив из 3 массивов из 4 int выше), корректируется; другие типы в его составе не корректируются.

Помимо

Стандарт C не совсем ясен в отношении эффектов регулировки. Используя Apple LLVM 10.0.1 с clang-1001.0.46.4, следующая программа выводит «Hello, world.»:

#include <stdio.h>

static void foo(int a[printf("Hello, world.\n")]) {}

int main(void) { foo(0); }

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

0 голосов
/ 25 мая 2019

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

Цитата C11, глава §6.3.2.1

За исключением случаев, когда это операнд оператора sizeof, оператора _Alignof или унарный оператор &, или строковый литерал, используемый для инициализации массива, выражение, которое имеет Тип ‘‘ массив типа ’’ преобразуется в выражение с типом ‘‘ указатель на тип ’’ , который указывает к начальному элементу объекта массива и не является lvalue. [...]

Итак, в вашем случае, внутри void test(char c[]) вызова функции, c это просто еще один указатель, который указывает на первый элемент массива. Обычная арифметика указателя может быть выполнена на этом указателе.

Другими словами,

 void test(char c[]) { //....

совпадает с

 void test(char *c) { //....

Итак, ваш случай чем-то похож на

int main(void)  //correcting the definition
{
    char ch[5]={'p','o','u','r'};
    //ch = ch+2;  //this is definitely not allowed on array names as they are not pointers

    char *c = &ch[0];   // this is what happens when you pass the array as function argument.
    c = c + 2;   // see, this is possible.

    test(ch);

    return 0;
}
0 голосов
/ 25 мая 2019

Обозначения массива являются неизменяемыми значениями.То есть вы не можете изменить указатель массива таким образом, чтобы он определял другой массив.

Рассматривайте указатели массива как именованные экстенты памяти.

Что касается вашего примера, то это объявление функции

void test(char c[]);

корректируется компилятором следующим образом

void test(char *c);

, то есть параметр с типом массива корректируется компилятором на указатель.Так, например, эти объявления функций

void test(char c[100]);
void test(char c[10]);
void test(char c[1]);
void test(char c[]);

эквивалентны и объявляют эту единственную функцию

void test(char *c);

Вы можете включить все эти объявления в вашу программу, хотя они будут избыточными.

Например,

#include <stdio.h>

void test(char c[100]);
void test(char c[10]);
void test(char c[1]);
void test(char c[]);
void test( char *c)
{
    c=c+2; 
    c--;
    printf("%c",*c);
}

int main( void )
{
    char ch[5]={'p','o','u','r'};
    test(ch);
}

Для наглядности рассмотрим следующую программу

#include <stdio.h>

void test( char c[] )
{
    printf( "sizeof( c ) = %zu\n", sizeof( c ) );
}

int main( void )
{
    char ch[5]={'p','o','u','r'};
    test( ch );
    printf( "sizeof( ch ) = %zu\n", sizeof( ch ) );
}

Его вывод

sizeof( c ) = 8
sizeof( ch ) = 5

То есть в функции sizeof( c )равен размеру указателя (в используемой системе он равен 8).В то время как в main sizeof( ch ) - это размер массива.

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

test( ch );
test( &ch[0] );

эквивалентны.

Это означает, что внутри функции вы имеете дело с указателем, и вы можете изменить значение указателя, используя арифметику указателя.

0 голосов
/ 25 мая 2019

Следует помнить, что имя массива «распадается» на указатель на его первый элемент.Это означает, что test(ch); эквивалентно test(&ch[0]);.

Кроме того, void test(char c[]) - это не что иное, как void test(char* c), указатель на символ.Указатели можно увеличивать или уменьшать, поэтому c = c + 2 и c-- компилируются просто отлично.

...