Дата и время публикации:
выражение, указывающее на потенциальный объект данных.
Под объектом данных понимаются переменные, массивы данных, константные и иные выражения, под которые была выделена память и явно назначен тип отличный от void. В противном случае, при попытке использовать lvalue, поведение не определено и обычно сборка заканчивается с ошибкой компилятора.
(п.6.3.2.1 ISO/IEC 9899:2x или более поздней редакции)
Таким образом, выражение lvalue должно иметь объект данных, которому назначен адрес, что подразумевается наличие доступ на протяжении всей времени жизни области видимости, как показано в листинге 1
Листинг 1
1 void* foo ( void *buf, char *label )
2 {
3 return (buf=label);
4 }
5
6
7typedef void* (*change_t) ( void *buf, char *label );
8
9struct some_entry
10{
11 int id;
12 char *name;
13};
14
15int main (int argc, char **argv)
16{
17/* Определение объектов данных */
18
19 float fps[3] = { 0.23445, 89.789, -879.12 };
20 int numirics [10] = { '0','1','2','3','4','5','6','7','8','9'};
21 char *hellop = "hello world!";
22 struct some_entry list[7] = {
23 { 0, "Peter" },
24 { 1, "John" },
25 { 2, "Robert" },
26 { 3, "Johann" },
27 { 4, "Lacksley" },
28 { 5, "Albert" },
29 { 6, "Hugh" },
30 };
31 int base = 2;
32 change_t callptr = change;
33 char *cstrp = 1+hellop,
34 *strp = --cstrp,
35 *nextp = ++cstrp;
36 int *np = numirics+(base+1);
37 float *fp = fps + 1;
38
39 struct some_entry *dp = list+(2*base+1);
...
48 return 0;
49}
50
51/*eof*/
Как показано в листинге 1, в строках 19,...,22 определяются массивы данных, которым выделяется память в секции данных .rodate, а в строках 31,...,35 непосредственно lvalue, результат совмещения с кодом ассемблера можно получить c помощью gasrunparts опции gnuc_locator_value (версии не ниже 15).
Согласно п.6.3.2.1 ISO/IEC 9899:2x, к модифицированным выражениям lvalue (англ. modifiable lvalue) относятся такие lvalue, которые не имеют неполных типов (англ. incomplete type), не объявлены с квалификатором const. Опять же, если тип является структурным или объединением, то он не должен содержать квалификатор const, как и включая, рекурсивно, любой член или элемент всех содержащихся составных типов или объединений.
Поэтому, как показано в листинге 2, если вдруг массив hellop в строке 21 листинга 1 обретет квалификатор const, а в строке 33 выражение останется неизменным,
Листинг 2
...
21 char *hellop = "hello world!";
...
33 char *cstrp = 1+hellop,
все закончиться большим пуком в виде ошибки на выходе компилятора, которая показана в дампе 1
Дамп 1
. . .
user@home:~/Projects/gasrunparts-0.15$ src/gasrunparts gnuc_locator_value
Compile "/usr/local/share/gasrunparts/examples/gnuc_lvalue.c" in dir "/home/user/Projects/gasrunparts-0.15"
/usr/local/share/gasrunparts/examples/gnuc_lvalue.c: In function ‘main’:
/usr/local/share/gasrunparts/examples/gnuc_lvalue.c:33:26: error: initialization discards ‘const’ qualifier from pointer target type [-Werror=discarded-qualifiers]
char *cstrp = 1+hellop,
^
cc1: all warnings being treated as errors
error: can't compile THIS!
Failure
. . .
В прочем, если все-таки выражение lvalue относится к модифицируемым, и планируется использовать некоторые операторы, необходимо не забывать о накладываемых к ним ограничениям, перечисленных в таблице 1
Оператор | Требования |
---|---|
& (унарный) | Операнд должен быть lvalue |
++, -- (инкремента,декремента) | Операнд должен быть lvalue. Требование применяется как постфиксной, так и префиксной формам записи, но не применима к типам объектов, имеющих вещественный тип (float,double) и агрегатный, т.е. составной как в случае со структурами. |
=, +=, -=, *=, %=, <<=, >>=, &=, ^=, |= | Первый операнд, слева должен быть lvalue |
Поэтому нам не возбраняется применять операцию инкремента (++), например к массиву (hellop) в строке 33 листинга 1, как показано в листинге 3
Листинг 3
...
33 char *cstrp = 1+hellop++,
...
Но ни в коим случае, употреблять с вещественным типом (float,double) и агрегатным, например к структуре some_entry, как показано в листинге 3а
Листинг 3a
37 float *fp = fps--;
...
39 struct some_entry *dp = ++list+(2*base+1);
...
Такая варварская форма записи непременно приведет к сообщениям, показанным в дампе 1а
Дамп 1а
. . . /usr/local/share/gasrunparts/examples/gnuc_lvalue.c:41:29: error: lvalue required as decrement operand float *fp = fps--; ^~ /usr/local/share/gasrunparts/examples/gnuc_lvalue.c:43:26: error: lvalue required as increment operand struct some_entry *dp = ++list +(2*base+1); . . .
В тоже время, не возбраняется производить обе операции инкремента (++) и декремента (--) в постфиксной и префиксной форме записи, как показано в строках 34, 35 листинга 1
А вот употреблять спецификатор класса хранения объекта register не следует. Особенно, если не предполагается объект инициализировать,т.е. при объявлении переменной base не был применен оператор присваивания, как показано в листинге 4
Листинг 4
...
31 register int base;
...
36 int *np = & base;
...
Из которого, компилятор сделал печальный вывод с сообщением об ошибке, что регистровой переменной base хорошо бы иметь адрес на реальный объект. Полный текст "обшибочного" сообщения компилятора приведен в дампе 2
Дамп 2
. . . /usr/local/share/gasrunparts/examples/gnuc_lvalue.c:40:2: error: address of register variable ‘base’ requested int *np = &base; ^~~ . . .
Конверсия от lvalue к rvalue производится, когда компилятор ждет от lvalue перевоплощения в rvalue, которое будет произведено согласно условиям предшествующие ситуациям, перечисленным в таблице 2 .
Благоприятные и не очень условия возникновения ситуации конверсии | Результирующее поведение |
---|---|
lvalue является функциональным типом | Соответсвует заявленным условиям |
lvalue является массивом | |
lvalue относится неполным типом | Закончится ошибкой во время компиляции |
lvalue ссылается на неинициализированный объект | Неопределенное поведение. |
lvalue ссылается на объект не являющимся rvalue или производным от него. |
(п.6.5.4/5 ISO/IEC 9899:2x или более поздней редакции)
Листинг 5
...
40 /* conversion rvalue to rvalue */
41 if ( callptr != NULL )
42 {
43 hellop = callptr(strp,"this is test!");
44 }
45
46 printf ( "\n\n!!! hellop=[%s] !!! \n\n", hellop );
47
...
В результате неявных преобразований типа, которые вылились в конвертирование lvalue к rvalue, как показано в строках 41 и 43 листинга 5.
Таким образом,
В первой строке реализуется соотношение функция-указатель, потому что проверяется наличие адреса, присвоенного rvalue в строке 32 листинга 1
Во второй строке, соответственно, имеем дело со вторым соотношением, т.к. по функциональному указателю callptr будет вызвана функция change(), который вернет указатель на массив "this is test!", в виде результирующего значения, возвращаемого этой функции, а затем присваиваемого указателю hellop. Что и иллюстрирует дамп 3 выполнение gasrunparts c помощью опции gnuc_locator_value (версии не ниже 15).
Дамп 3
user@home:~/Projects/gasrunparts-0.15$ src/gasrunparts gnuc_locator_value Compile "/usr/local/share/gasrunparts/examples/gnuc_lvalue.c" in dir "/home/user/Projects/gasrunparts-0.15" Running "gnuc_lvalue" in dir "/home/user/Projects/gasrunparts-0.15 " !!! hellop=[this is test!] !!! retcode: 0 == Press key Enter to continue == . . .
Библиография
3.1 Understanding lvalues and rvalues in C and C++
3.2 The Tutorials point. C - Variables