Приведение указателя как lvalue - PullRequest
4 голосов
/ 20 апреля 2020

Почему (int*) p = &x; не является действительным утверждением? в то время как *(int*) p = &x; является действительным утверждением без предупреждений? Я знаю, что приведение является rvalue , но как второе утверждение было скомпилировано без предупреждений?

Ответы [ 3 ]

3 голосов
/ 20 апреля 2020
(int*) p = &x;

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

*(int*) p = &x;

Это допустимо, поскольку результатом оператора разыменования * является lvalue. Разыменование указателя дает вам объект, на который указывает указатель.

1 голос
/ 20 апреля 2020

Интуитивно говоря, lvalue - это выражение, которое означает «вот объект честности к добру», тогда как rvalue - это выражение, которое означает «вот значение, которое не является объектом». (Различие немного мрачнее этого, но на данный момент это разумное упрощение.) То есть lvalue - это поле, в которое можно что-то поместить, а rvalue - это просто значение в этом поле.

Например, выражение 137 является значением r, так как это чистое число, а не поле, содержащее число. Если x является переменной, то выражение x является lvalue, потому что оно представляет поле и число в нем, но выражение x + 137 является rvalue, поскольку оно просто представляет число, а не поле, содержащее число.

Как это учитывается? Предположим, что x является int. Тогда (float)x является r-значением, потому что оно просто представляет значение - в частности, это «число, которое вы получили бы, если бы взяли x и сделали наименее ужасную вещь, которую можно представить как float». Это не коробка, в которую можно что-то положить.

Это объясняет, почему

(int *)p = &x;

не работает. (int *)p - это r-значение - чистое значение, а не то, что вы можете присвоить - и вы пытаетесь воспринимать это как поле, в которое вы можете поместить что-то.

С другой стороны, результат разыменование указателя является lvalue, так как оно представляет «посмотрите туда! это поле, в которое вы можете что-то вставить». В этом смысле вы можете написать

*(int *)p = x;

, потому что это означает

  1. Оценить выражение (int *)p. Хорошо, теперь у нас есть значение r, и это указатель на где-то в памяти, который содержит int.
  2. Разыменование этого указателя для получения *(int *)p. Хорошо, теперь у нас есть lvalue, представляющее это целое число.
  3. Материал x в этом месте. Это нормально - *(int *)p - это поле.

Могут быть и другие проблемы с этим кодом:

*(int *)p = &x;

Здесь RHS этого выражения имеет тип (int *), который является указателем. LHS этого выражения имеет тип int (вы разыменовали указатель на целое число), поэтому вы конвертируете указатель в целое число. Таким образом, это означает, что либо отсутствует приведение, либо вы делаете неправильную вещь с этим кодом.

Вторая проблема заключается в том, что

*(int *)p

означает "интерпретировать p как хотя он говорит вам, где в памяти можно найти int, тогда напишите что-нибудь там. " Если p не указатель, это почти наверняка приведет к взлому sh вашего кода, потому что вы будете писать в произвольном месте в памяти. И если p является указателем, то, возможно, лучше разыграть &x, чем p?

p = (T*) &x;

, где T - это тип указанной вещи по p.

0 голосов
/ 21 апреля 2020

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

Что такое lvalue? То, что имеет адрес. В первом случае у вас есть указатель. Во втором случае вы разыменовываете указатель.

Что такое указатель? Указатель является адресом. Адрес сам по себе не имеет адреса (ну, давайте не будем здесь слишком технически). В вашем примере предположим, что p - это константа 1235. Каков ее адрес? Нет, это просто константа, она может не существовать в памяти. Может быть, это в реестре. Может быть, компилятор полностью его оптимизировал. Более того, ваше выражение (int*)p = &x говорит: «возьмите адрес 1235 и присвойте ему адрес x, который может быть чем-то вроде 6789». Другими словами, «измените адрес с 1235 на 6789». Какая?! Изменить адрес?!

Что такое разыменованный указатель? Это содержимое указателя, то есть значение по адресу , на который указывает указатель. Теперь у этого, по определению, есть адрес! Вот что происходит во втором утверждении. Он говорит: «установите содержимое по адресу, указанному p на &x». Поэтому в моем примере написано «установить значение по адресу памяти 1235 на 6789». Теперь это имеет смысл! Вот почему это lvalue.

Надеюсь, это имеет смысл.

...