Я буду обсуждать вещи в контексте вашего кода, но сначала я хочу немного разобраться с основами.
В объявлении унарный оператор *
указывает, что объявленная вещь имеет тип указателя:
T *p; // for any type T, p has type "pointer to T"
T *p[N]; // for any type T, p has type "N-element array of pointer to T"
T (*p)[N]; // for any type T, p has type "pointer to N-element array of T"
T *f(); // for any type T, f has type "function returning pointer to T"
T (*f)(); // for any type T, f has type "pointer to function returning T"
Унарный оператор *
имеет более низкий приоритетзатем операторы постфикса []
subscript и ()
function, так что если вы хотите указатель на массив или функцию, *
должен быть явно сгруппирован с идентификатором.
В выражении унарный оператор *
разыменовывает указатель, позволяя нам получить доступ к объекту или функции, на которую указывает указатель:
int x;
int *p;
p = &x; // assign the address of x to p
*p = 10; // assigns 10 to x via p - int = int
После выполнения вышеуказанного кода выполняются следующие условия:
p == &x // int * == int *
*p == x == 10 // int == int == int
Выражения p
и &x
имеют тип int *
(указатель на int
), а их значением является (виртуальный) адрес x
.Выражения *p
и x
имеют тип int
, а их значение равно 10
.
Действительное 1 значение указателя объекта получается одним из трех способов (указатели на функции также важны, но мы не будем вдаваться в них здесь):
- с использованием унарного оператора
&
в lvalue 2 (p = &x;
); - выделение динамической памяти через
malloc()
, calloc()
или realloc()
; - и, что важно для вашего кода, с использованием массива выражений без оператора
&
или sizeof
.
За исключением случаев, когда это операнд оператора sizeof
или унарный &
или строковый литерал, используемый для инициализации массива символов в объявлении, выражение типа "массив N-элементов из T
" преобразуется ("распадается") в выражение типа "указатель на T
", а значением этого выражения является адрес первого элемента массива 3 .Таким образом, если вы создаете массив типа
int a[10];
и передаете выражение этого массива в качестве аргумента функции, подобной
foo( a );
, то перед вызовом функции выражение a
преобразуется из типа «10-элементный массив int
» в «указатель на int
», а значение a
является адресом a[0]
.Таким образом, функция фактически получает значение указателя, а не массив:
void foo( int *a ) { ... }
Строковые литералы, такие как "add"
и "five to two"
, являются выражениями массива - "add"
имеет тип "4-элементный массив из char
"и "five to two"
имеет тип" массив из 12 элементов char
"(для строки из N символов требуется не менее N + 1 элементов для хранения из-за ограничителя строки).
В операторах
mnemonic = "add";
operands = "five to two";
ни строковый литерал не является операндом операторов sizeof
или унарных &
, и они не используются для инициализации массива символов вобъявление, поэтому оба выражения преобразуются в тип char *
, а их значения являются адресами первого элемента каждого массива.И mnemonic
, и operands
объявлены как char *
, так что это нормально.
Поскольку типы mnemonic
и operands
оба char *
, при вызове
analyse_inst( mnemonic, operands );
типы формальных аргументов функции также должны быть char *
:
void analyse_inst( char *mnemonic, char *operands )
{
...
}
Что касается бита "передача по ссылке" ...
C передает все аргументы функции по значению .Это означает, что формальный аргумент в определении функции является объектом, отличным от реального аргумента в вызове функции, и любые изменения, внесенные в формальный аргумент, не отражаются в фактическом аргументе.Предположим, мы пишем функцию swap
как:
int swap( int a, int b )
{
int tmp = a;
a = b;
b = tmp;
}
int main( void )
{
int x = 2;
int y = 3;
printf( "before swap: x = %d, y = %d\n", x, y );
swap( x, y );
printf( "after swap: x = %d, y = %d\n", x, y );
...
}
Если вы скомпилируете и запустите этот код, вы увидите, что значения x
и y
не изменяются после вызоваswap
- изменения в a
и b
не повлияли на x
и y
, потому что это разные объекты в памяти.
Чтобы функция swap
работала, мы должны передать указатели на x
и y
:
void swap( int *a, int *b )
{
int tmp = *a;
*a = *b;
*b = tmp;
}
int main( void )
{
...
swap( &x, &y );
...
}
В этом случае выражения *a
и *b
в swap
относятся к тем же объектам, что и выражения x
и y
в main
, поэтому изменения в*a
и *b
отражаются в x
и y
:
a == &x, b == &y
*a == x, *b == y
Итак, в целом:
void foo( T *ptr ) // for any non-array type T
{
*ptr = new_value(); // write a new value to the object `ptr` points to
}
void bar( void )
{
T var;
foo( &var ); // write a new value to var
}
Это также верно для типов указателей - заменитеT
с указателем типа P *
, и мы получаем следующее:
void foo( P **ptr ) // for any non-array type T
{
*ptr = new_value(); // write a new value to the object `ptr` points to
}
void bar( void )
{
P *var;
foo( &var ); // write a new value to var
}
В этом случае var
сохраняет значение указателя.Если мы хотим записать новое значение указателя в var
- foo
, то мы все равно должны передать указатель в var
в качестве аргумента.Поскольку var
имеет тип P *
, то выражение &var
имеет тип P **
.
Значение указателя допустимо, если оно указывает на объект в течение времени жизни этого объекта. lvalue - это выражение, которое ссылается на объект, так что значение объекта может быть прочитано или изменено. Верьте или нет, есть веская причина для этого правила, но это означает, что выражения массива теряют свою "массивность" в большинстве случаев, что приводит к большой путанице среди людей, впервые изучающих язык.