2013-08-23 4 views
7

После прочтения главы о структурах в R книге K & я решил сделать несколько тестов, чтобы лучше понять их, так что я написал этот кусок кода:Задание законного массива. Является ли это возможным?

#include <stdio.h> 
#include <string.h> 

struct test func(char *c); 

struct test 
{ 
    int i ; 
    int j ; 
    char x[20]; 
}; 

main(void) 
{ 
    char c[20]; 
    struct {int i ; int j ; char x[20];} a = {5 , 7 , "someString"} , b; 
    c = func("Another string").x; 
    printf("%s\n" , c); 
} 

struct test func(char *c) 
{ 
    struct test temp; 
    strcpy(temp.x , c); 
    return temp;  
} 

Мой вопрос: почему c = func("Another string").x; работает (I знаете, что это незаконно, но почему это работает)? Сначала я написал его, используя strcpy() (потому, что казалось самым логичным, что нужно сделать), но я продолжал иметь эту ошибку:

structest.c: In function ‘main’: 
structest.c:16:2: error: invalid use of non-lvalue array 
+3

«Мой вопрос - почему« c = func («Другая строка»). X; 'legal 'Это не так. По крайней мере, не в C. Массивы не назначаются. –

+0

Вы пробовали использовать код? –

+2

Как я могу запустить его, если он не скомпилирован? clang: 'error: array type 'char [20]' не присваивается', gcc:' error: несовместимые типы при назначении типа 'char [20]' из типа 'char *' ' –

ответ

6
char c[20]; 
    ... 
    c = func("Another string").x; 

Это не правильный код C. Не в C89, а не в C99, а не в C11.

Видимо он компилирует с последними версиями gcc4.8 в режиме -std=c89 без диагностики для назначения (clang вопросы диагностики). Это ошибка в gcc при использовании в режиме C89.

Соответствующие цитаты из C90 стандарта:

6.2.2.1 "A modifiable lvalue is an lvalue that does not have array type, does not have an incomplete type, does not have a const-qualified type. and if it is a structure or union. does not have any member (including. recursively, any member of all contained structures or unions) with a const-qualified type."

и

6.3.16 "An assignment operator shall have a modifiable lvalue as its left operand."

6.3.16 является ограничение и налагает по крайней мере gcc выпустить диагностический, который gcc не делает, так это Жук.

+0

Я хорошо знаю, что это незаконно, но почему это работает, я имею в виду, даже если это ошибка в 'gcc', почему она возвращает именно то, что я хочу, чтобы она возвращала не какие-то случайные вещи ? –

+0

@FaroukJouti одно объяснение может быть разрешено в gnu89 и 'gcc'« забывает », чтобы запретить его в режиме c89. – ouah

+0

это известная ошибка? –

0

OP: но почему это работает?
Потому что , очевидно, при копировании поля структуры, имеет значение только тип и размер.
Я буду искать документ, чтобы поддержать это.

[Изменить] Рассмотрение C11 6.3.2 относительно назначений, LValue C, поскольку это массив, это адрес этого массива, который становится местом хранения задания (без шок). Дело в том, что результатом функции является значение выражения, а ссылка подполя также является значением выражения. Тогда этот странный код разрешен, потому что он просто присваивает значение выражения (20-байты) в пункт назначения местоположение &c[0] , что также является символом [20].

[Edit2] Суть в том, что результат func().x является значением (значения выражения) и что является законным назначением для типа соответствияchar[20] на левой стороне. В то время как c = c сбой для c с правой стороны (символ [20]), становится адресом массива, а не всем массивом и, следовательно, не назначается char[20]. Это так странно.

[Edit3] Этот не удается с gcc -std=c99.

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

ct = func("1 Another string") выглядит хорошо. Одна структура была скопирована en masse в другую.

ct.x = func("2 Another string").x начинает выглядеть подозрительно, но на удивление работает. Я ожидаю, что правая половина будет в порядке, но назначение массива массиву выглядит неправильно.

c = func("3 Another string").x как и предыдущий. Если предыдущее было хорошо, это тоже летит. Интересно, что если c был размером 21, компиляция не удалась.

Примечание: c = ct.x не удается скомпилировать.

#include <stdio.h> 
#include <string.h> 

struct test { 
    int i; 
    char x[20]; 
}; 

struct test func(const char *c) { 
    struct test temp; 
    strcpy(temp.x, c); 
    return temp; 
} 

int main(void) { 
    char c[20]; 
    c[1] = '\0'; 
    struct test ct; 
    ct = func("1 Another string"); 
    printf("%s\n" , ct.x); 
    ct.x = func("2 Another string").x; 
    printf("%s\n" , ct.x); 
    c = func("3 Another string").x; 
    printf("%s\n" , c); 
    return 0; 
} 

1 Another string 
2 Another string 
3 Another string 
+0

Какова ошибка? –

+0

, когда вы компилируете, какие сообщения об ошибках вы получаете –

+0

хорошо, тогда что вы подразумеваете под «Примечание: c = ct.x не скомпилируется». –

1

Эта линия

c = func("Another string").x; 

с c быть объявлен как

char c[20]; 

не действует C в любой версии C. Если она "работает" в ваш случай, это либо ошибка компилятора, либо довольно странная расширение компилятора.

В случае strcpy

strcpy(c, func("Another string").x); 

соответствующая деталь природа func("Another string").x подвыражения. В «классическом» C89/90 это подвыражение не может быть подвергнуто преобразованию массива в указатель, так как в преобразовании матрицы к указателю C89/90 применяется только к массивам lvalue только. Между тем, ваш массив является rvalue, он не может быть преобразован в const char * тип, ожидаемый вторым параметром strcpy. Именно об этом сообщает сообщение об ошибке.

Эта часть языка была изменена на C99, что позволяет преобразовывать массив в указатель для массивов rvalue. Итак, в C99 будет скомпилировано выше strcpy.

Другими словами, если ваш компилятор выдает ошибку для вышеуказанного strcpy, это должен быть старый компилятор C89/90 (или новый компилятор C в строгом режиме C89/90). Вам нужен компилятор C99 для компиляции такого вызова strcpy.

0

Есть две ошибки в вас код:

main(void) 
{ 
     char c[20]; 
     struct { int i ; int j ; char x[20];} a = {5 , 7 , "someString"} , b; 
     c = func("Another string").x;// here of course number one 
     printf("%s\n" , c); 
} 
struct test func(char *c) 
{ 
     struct test temp; 
     strcpy(temp.x , c); 
     return temp;   // here is number two , when the func finished the memory of function func was freed, temp is freed also. 

} 

Написать вам код, как это:

main(void) 
{ 
     struct test *c; 
     struct { int i ; int j ; char x[20];} a = {5 , 7 , "someString"} , b; 
     c = func("Another string"); 
     printf("%s\n" , c->x); 
     free(c);        //free memory 
} 
struct test * func(char *c) 
{ 
     struct test *temp = malloc(sizeof(struct test));//alloc memory 
     strcpy(temp->x , c); 
     return temp; 
} 
2

Это ошибка в НКУ.

Выражение типа массива в большинстве контекстов неявно преобразуется в указатель на первый элемент объекта массива. Исключениями являются выражения: (a) операнд унарного оператора sizeof; (b) когда это операнд унарного оператора &; и (c) когда это строковый литерал в инициализаторе, используемом для инициализации объекта массива. Ни одно из этих исключений не применяется здесь.

В этом описании есть лазейка.Он предполагает, что для любого заданного выражения типа массива существует объект массива , к которому он относится (т. Е. Что все выражения массива являются lvalues). Это почти true, но есть один угловой случай, с которым вы столкнулись. Функция может возвращать результат типа структуры. Этот результат - просто значение типа struct, не относящееся ни к какому объекту. (Это в равной степени относится к профсоюзам, но я буду игнорировать это.)

Это:

struct foo { int n; }; 
struct foo func(void) { 
    struct foo result = { 42 }; 
    return result; 
} 

ничем не отличается в принципе от этого:

int func(void) { 
    int result = 42; 
    return result; 
} 

В обоих случаях, копия Возвращается из result; это значение может быть использовано после прекращения существования объекта result.

Но если возвращаемая структура имеет член типа массива, то у вас есть массив, который является членом структуры, отличной от lvalue, что означает, что вы можете иметь выражение без lvalue.

В C90 и C99 попытка обращения к такому массиву (если только он не является операндом sizeof) имеет неопределенное поведение - не потому, что стандарт говорит так, а потому, что он не определяет поведение.

struct weird { 
    int arr[10]; 
}; 
struct weird func(void) { 
    struct weird result = { 0 }; 
    return result; 
} 

Вызов func() дает выражение типа struct weird; в этом нет ничего плохого, и вы можете, например, назначить его объекту типа struct weird. Но если вы пишете что-то вроде этого:

(void)func().arr; 

то стандарт говорит, что выражение массива func().arr преобразуется в указатель на первый элемент объекта несуществующего, к которому оно относится. Это не просто случай неопределенного поведения путем упущения (который стандарт явно указывает на неопределенное поведение). Это ошибка в стандарте. В любом случае стандарт не может определить поведение.

В стандарте ISO C 2011 года (C11) комитет окончательно признал этот угловой корпус и создал концепцию временного срока службы . N1570 6.2.4p8 говорит:

A non-lvalue expression with structure or union type, where the structure or union contains a member with array type (including, recursively, members of all contained structures and unions) refers to an object with automatic storage duration and temporary lifetime Its lifetime begins when the expression is evaluated and its initial value is the value of the expression. Its lifetime ends when the evaluation of the containing full expression or full declarator ends. Any attempt to modify an object with temporary lifetime results in undefined behavior.

сноска:

The address of such an object is taken implicitly when an array member is accessed.

Таким образом, решение C11 в это затруднительное положение было создать временный объект, так что преобразование массива к указателю будет на самом деле введите адрес чего-то значимого (элемент члена объекта с временным временем жизни).

Очевидно, что код в gcc, который обрабатывает этот случай, не совсем прав. В режиме C90 он должен сделать что-то, чтобы обойти несогласованность в этой версии стандарта. По-видимому, он обрабатывает func().arr как выражение не-lvalue-массива (которое может быть корректным в соответствии с правилами C90), но затем он неправильно разрешает присвоение массива объекту массива. Попытка назначить объект массива, независимо от выражения в правой части присваивания, явно нарушает раздел ограничения в C90 6.3.16.1, для которого требуется диагностика, если LHS не является lvalue арифметики, указателя, структуры или типа соединения. Непонятно (из правил C90 и C99), должен ли компилятор диагностировать выражение, например func().arr, но он явно должен диагностировать попытку присвоить это выражение объекту массива, либо на C90, C99, либо на C11.

По-прежнему остается загадкой, почему эта ошибка появляется в режиме C90, пока она правильно диагностирована в режиме C99, поскольку, насколько я знаю, существенных изменений в этой области стандарта между C90 и C99 не было (временный срок службы был введен только в C11). Но поскольку это ошибка, я не думаю, что мы можем слишком много жаловаться на это, показывая непоследовательность.

Обход проблемы: не делайте этого.