2014-02-14 2 views
5

Я пытаюсь создать оболочку UNIX в C. Если бы она была на Java, это был бы кусок пирога, но я не так опытен в C. Массивы в C меня немного путают. Я не уверен, как объявить или получить доступ к определенным структурам данных.Создание массивов в C

Я хотел бы создать строку для чтения в каждой строке. Достаточно просто: просто массив символов. Я хотел бы инициализировать его следующим образом:

char line[256]; //Maximum size of each line is 255 characters 

А для доступа к элементу этого массива, я бы сделал так:

line[0] = 'a'; //Sets element 0 to 'a' 
fgets(line, sizeof line, stdin); //Gets a line from stdin and places it in line 

Как объявить и использовать строку в этой манере отличается от объявления его как указатель? По моему мнению, массив в C распадается на указатель. Итак, будет ли эквивалентно следующее?

char *line = (char*) malloc(sizeof(char) * 256); 
line[0] = 'a'; 
fgets(*line, sizeof(line), stdin); 

Когда вы используете символ указателя '*', а когда нет? В приведенном выше примере включается «*» в fgets, необходимо или правильно?

Теперь я хотел бы создать массив строк или, вернее, массив указателей, указывающих на строки. Сделаю ли я это следующим образом?

char *arr[20]; // Declares an array of strings with 20 elements 

И как я могу получить к нему доступ?

arr[0] = "hello" // Sets element zero of arr to "hello" 

Это правильно?

Как передать этот массив функции?

execvp("ls", arr); // Executes ls with argument vector arr 

Это правильно, или я бы использовал указатель * arr? Если да, то почему?

Теперь еще хуже, мне нужен массив массивов строк (например, если бы я хотел хранить несколько векторов аргументов, чтобы выполнить несколько команд в последовательности труб). Будет ли это заявлено следующим образом?

char **vector_arr[20]; // An array of arrays of strings 

И как бы получить доступ к элементу этого массива?

execvp("ls", vector_arr[0]); // Executes ls with first element of vector_arr as argument vector 

Я думал, что я понял приличное понимание того, что указатель, и даже как массивы относятся к указателям, но я, кажется, возникли проблемы, касающиеся этого фактического кода. Я предполагаю, что при работе с указателями я не знаю, когда обращаться к * var, var или & var.

+3

Слишком много вопросов здесь. Предложите прочитать хорошую книгу C. – OldProgrammer

+0

На самом деле, я думаю, что OP удалось ударить по большинству всех вопросов с массивом/строкой в ​​одном сообщении и сделать это довольно логично и красноречиво. Должна ли почта следить за сайтом и диктовать его? Может быть. Но хороший ответ будет полезной ссылкой на одной странице. – Duck

+0

Это может сделать приятную ссылку, но я не уверен, что StackOverflow пишет о ссылках/учебниках? – keshlam

ответ

3

Вы действительно на правильном пути.

В вашем втором примере, где используется malloc(), команда fgets() будет называться так:

fgets(line, sizeof(line), stdin); /* vs. fgets(*line ...) as you have */

Причина этого заключается в том, что в C именованный массив переменных всегда просто указатель. Итак:

char line[256];

заявляет (и определяет) указатель, называемый line, который указывает на 256 байтов памяти, выделенных во время компиляции (вероятно, в стеке).

char *line; также объявляет указатель, но память, на которую указывает точка, не назначается компилятором. Когда вы вызываете malloc, вы выводите возвращаемое значение на char * и назначаете его line, чтобы память распределялась динамически в куче.

Функционально, хотя, переменная line просто char * (указатель на символ), и если вы посмотрите на объявления fgets в файле <stdio.h>, вы увидите, что он ожидает, что в качестве первого аргумента:

char *fgets(char * restrict str, int size, FILE * restrict stream);

... а именно char *. Таким образом, вы можете передать line любым способом, который вы его объявили (как указатель или как массив).

Что касается других ваших вопросов:

char *arr[20]; объявляет 20 неинициализированных указателей на char *.Для того, чтобы использовать этот массив, вы итерацию в 20 раз по элементам arr и назначить каждый из них с каким-то результатом malloc():

arr[0] = (char *) malloc(sizeof(char*) * 256); 
arr[1] = (char *) malloc(sizeof(char*) * 256); 
... 
arr[19] = (char *) malloc(sizeof(char*) * 256); 

Тогда вы могли бы использовать каждый из 20 строк. Для того, чтобы пройти второй к fgets, которая ожидает в char * в качестве первого аргумента, вы могли бы сделать это:

fgets(arr[1], ...);

Тогда fgets получает char * он ожидает.

Помните, что вам необходимо позвонить malloc(), прежде чем вы это сделаете, или arr[1] будет неинициализирован.

Ваш пример использования execvp() является правильным (если вы выделили все эти строки с malloc() первым. vector_arr[0] это символ **, которые execvp() ожидает. [Помните также execvp() ожидает последний указатель вашего векторного массива, чтобы иметь значение NULL, обратитесь к странице разъяснений].

Обратите внимание, что execvp() объявлен как так (см <unistd.h>)

int execvp(const char *file, char *const argv[]);

удаление атрибута const для ясности, он также может быть объявлен следующим образом:

int execvp(const char *file, char **argv);

Декларация char **array быть функционально эквивалентны char *array[]. Помните также, что в каждом примере, где мы используем malloc(), вам необходимо в какой-то момент использовать соответствующий free() или вы будете утечка памяти.

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

+0

Итак, из моего понимания, объявление массива с использованием априорной записи [int] делает массив постоянным. В случае строки я должен был бы использовать строковые функции (strcpy, strcat) для изменения строки, правильно? При объявлении как указателя, но * arr, массив является динамическим. Вам нужно использовать malloc для создания пространства для значения, прежде чем назначать его? Или присваивание значения массиву создает пробел? Спасибо за ваш ответ, это очень помогло. – Johndt6

+1

Если вы не используете ключевое слово 'const', содержимое массива равно * not * constant. 'const' - отдельная тема, так что не беспокойтесь об этом. Независимо от того, каким образом вы объявляете 'line' (с malloc или нет), вы все равно можете сказать' line [0] = 'a'; '(попробуйте!). Просто не передавайте тот, который был выделен компилятором 'free()'. – par

+2

* делает массив постоянным * - это плохой способ думать об этом. Подумайте об этом как 'arr [int]' имеет назначенный фиксированный адрес. С '* arr' переменная указателя arr имеет фиксированный адрес, но то, на что указывает (значение), является переменной и может меняться. – Duck

2

Отчасти ответьте на ОП.

char *line = (char*) malloc(sizeof(char) * 256); 
line[0] = 'a'; 
fgets(*line, sizeof(line), stdin); 

аргументы в fgets() неправильно, он должен быть fgets(line, 256, stdin);.

Объяснение:

  1. fgets() ожидает свой первый аргумент char *, так что вы можете использовать указатель на char или массив char (это имя массива будет разлагаться char * в данном случае).

    При использовании в качестве аргумента функции имя массива будет деградировать до указателя.

  2. becuase line - указатель, sizeof(line) предоставит вам размер указателя (обычно 4 в 32-битной системе); но если line - это массив, такой как char line[100], sizeof(line), вы получите размер массива, в данном случае 100 * sizeof (char).

    При использовании в качестве аргумента оператора sizeof имя массива не будет деградировать указатель.

+0

Спасибо за ваш ответ. Итак, для размера строки *, я бы хотел использовать strlen (line) вместо этого, правильно? Если бы мне нужен размер массива строк, тогда мне пришлось бы хранить размер, так как sizeof не работал бы? – Johndt6

+1

@JohnT Вы не можете использовать 'strlen (line)', чтобы получить длину 'line', becuase' strlen() 'ожидает строку в C, это означает последовательность символов и' \ 0', но содержимое этой памяти «mallac» неизвестно, это может быть что угодно. –

+1

Вы как бы пытаетесь бежать, прежде чем идти, но strlen() сообщит вам логическую длину строки (это то, что вы хотите), а не физическую длину. В C вы обозначаете конец строки, если последний байт установлен на ноль. Итак, 'line [0] = 'a'; line [1] = 0; 'создаст строку, логически одну длину символа, а strlen() вернет 1. Обратите внимание, что вам действительно нужно было использовать два байта, один для« a »и один для NULL (ноль) терминатор. И * что * ничего не говорит о физическом размере линии, который, как мы знаем, составляет 256 байтов! – par

4

Давайте поговорим о выражений и типы, как они относятся к массивам в С.

Массивы

Когда вы объявляете массив как

char line[256]; 

выражение line имеет тип "256-элементный arr ay от char "; кроме случаев, когда это выражение является операндом операторов sizeof или унарных &, оно будет преобразовано («распад») в выражение типа «указатель на char», а значение выражения будет адресом первого элемента массив.С учетом вышеизложенной декларацией, все из следующих условий:

Expression    Type   Decays to   Equivalent value 
----------    ----   ---------   ---------------- 
     line    char [256]  char *    &line[0] 
     &line    char (*)[256] n/a     &line[0] 
     *line    char   n/a     line[0] 
    line[i]    char   n/a     n/a 
    &line[0]    char *   n/a     n/a 
sizeof line    size_t   n/a     Total number of bytes 
                   in array (256) 

Обратите внимание, что выражения line, &line и &line[0] все дают то же значения (адрес первого элемента массива является таким же, как адрес самого массива), это просто, что типы разные. В выражении &line выражение массива является операндом оператора &, поэтому приведенное выше правило преобразования не применяется; вместо указателя на char, мы получаем указатель на массив из 256 элементов из char. Тип вопросов; если вы пишете что-то вроде следующего:

char line[256]; 
char *linep = line; 
char (*linearrp)[256] = &line; 

printf("linep + 1 = %p\n", (void *) (linep + 1)); 
printf("linearrp + 1 = %p\n", (void *) (linearrp + 1)); 

вы получите разные выходные данные для каждой строки; linep + 1 дало бы адрес следующего char следующего line, в то время как linearrp + 1 дало бы адрес следующего 256-элементного массива char следующего line.

Выражение line не является модифицируемое значение; вы не можете назначить ему, так что-то вроде

char temp[256]; 
... 
line = temp; 

было бы незаконным. Для переменной line хранения не выделяется отдельно от line[0] до line[256]; нет ничего, чтобы назначить на.

Из-за этого, когда вы передаете выражение массива функции, то, что получает функция, является значением указателя, а не массивом. В контексте объявления параметра функции T a[N] и T a[] интерпретируются как T *a; все три объявляют a в качестве указателя на T. «Массивность» параметра была потеряна в ходе вызова.

Все обращения к массиву выполняются с точки зрения арифметики указателя; выражение a[i] оценивается как *(a + i). Выражение массива a сначала преобразуется в выражение типа указателя в соответствии с приведенным выше правилом, затем мы смещаем iэлементов с этого адреса и разыскиваем результат.

В отличие от Java, C не отложите память для указателя на массив отдельно от элементов массива сами: все, что отведены является следующее:

+---+ 
| | line[0] 
+---+ 
| | line[1] 
+---+ 
... 
+---+ 
| | line[255] 
+---+ 

также не C выделяет память для массивов из куча (для любого определения кучи). Если массив объявлен auto (то есть локальный для блока и без ключевого слова static), то память будет выделяться из того места, где реализация получает память для локальных переменных (большинство из нас называют стек). Если массив объявлен в области файла или с ключевым словом static, память будет выделена из другого сегмента памяти, и он будет выделен при запуске программы и удерживаться до тех пор, пока программа не завершится.

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

Указатели

При объявлении указатель как

char *line; 

выражение line имеет тип "указатель на char" (Дух). Для хранения адреса объекта char зарезервировано достаточное хранилище. Если вы не объявите его в области файлов или с помощью ключевого слова static, он не будет инициализирован и будет содержать некоторый случайный шаблон бита, который может соответствовать или не соответствовать действительному адресу. Учитывая вышеуказанное заявление, то все следующие условия:

Expression    Type   Decays to   Equivalent value 
----------    ----   ---------   ---------------- 
     line    char *   n/a     n/a 
     &line    char **   n/a     n/a 
     *line    char   n/a     line[0] 
    line[i]    char   n/a     n/a 
    &line[0]    char *   n/a     n/a 
sizeof line    size_t   n/a     Total number of bytes 
                   in a char pointer 
                   (anywhere from 2 to 
                   8 depending on the 
                   platform) 

В этом случае line и &line действительно дают нам разные значения, а также различные типы; line - простой скалярный объект, поэтому &line дает нам адрес этого объекта. Опять же, обращения к массиву выполняются в терминах арифметики указателя, поэтому line[i] работает так же, как строка объявляется как массив или как указатель.

Так что, когда вы пишете

char *line = malloc(sizeof *line * 256); // note no cast, sizeof expression 

это так, что работает как Java; Вы имеете отдельный переменный указатель, который ссылается на хранилище, направляемое из кучи, как так:

+---+ 
| | line -------+ 
+---+    | 
...    | 
+---+    | 
| | line[0] <---+ 
+---+ 
| | line[1] 
+---+ 
... 
+---+ 
| | line[255] 
+---+ 

В отличии от Java, C не будет автоматически вышлет эту память, когда нет больше ссылок на него. Вы должны явно освободить его, когда вы закончите с ним, используя функцию free библиотеки:

free(line); 

Что касается ваших конкретных вопросов:

fgets(*line, sizeof(line), stdin);

Когда используется символ указателя ' * ', а когда нет? В приведенном выше примере включается «*» в fgets, необходимо или правильно?

Неточная информация; fgets ожидает, что первый аргумент имеет тип «указатель на char»; выражение *line имеет тип char. Это следует из декларации:

char *line; 

Во-вторых, sizeof(line) только дает вам размер указателя, а не размер того, что указатель не на; если вы не хотите читать точно sizeof (char *) байт, вы должны будете использовать другое выражение, чтобы указать количество символов для чтения:

fgets(line, 256, stdin); 
Теперь я хотел бы создать массив строк, или, вернее, массив указателей, указывающих на строки. Сделаю ли я это следующим образом?
char *arr[20]; // Declares an array of strings with 20 elements

C не имеет отдельный "строка" тип данных так, как C++ или Java делать; в C, строка - это просто последовательность символов, заканчивающихся на 0. Они хранятся как массивы char. Обратите внимание, что все, что вы указали выше, представляет собой 20-элементный массив указателей на char; эти указатели могут указывать на вещи, которые не являются строками.

Если все струны будет иметь ту же максимальную длину, вы можете объявить 2D массив char так:

char arr[NUM_STRINGS][MAX_STRING_LENGTH + 1]; // +1 for 0 terminator 

, а затем вы бы назначить каждую строку как

strcpy(arr[i], "some string"); 
strcpy(arr[j], some_other_variable); 
strncpy(arr[k], MAX_STRING_LENGTH, another_string_variable); 

, хотя остерегайтесь strncpy; он не будет автоматически добавлять терминатор 0 в строку назначения, если исходная строка была длиннее адресата. Вы должны убедиться, что терминатор присутствует, прежде чем пытаться использовать его с остальной библиотекой строк.

Если вы хотите, чтобы выделить место для каждой строки отдельно, вы можете объявить массив указателей, а затем выделить каждый указатель:

char *arr[NUM_STRINGS]; 
... 
arr[i] = malloc(strlen("some string") + 1); 
strcpy(arr[i], "some string"); 
... 
arr[j] = strdup("some string"); // not available in all implementations, calls 
            // malloc under the hood 
... 
arr[k] = "some string"; // arr[k] contains the address of the *string literal* 
         // "some string"; note that you may not modify the contents 
         // of a string literal (the behavior is undefined), so 
         // arr[k] should not be used as an argument to any function 
         // that tries to modify the input parameter. 

Обратите внимание, что каждый элемент arr является значение указателя; указывают ли эти указатели на строки (0-концевые последовательности char) или не зависит от вас.

Теперь еще хуже, мне нужен массив массивов строк (например, если бы я хотел хранить несколько векторов аргументов, чтобы выполнить несколько команд в последовательности труб). Будет ли оно объявлено следующим образом?
char **vector_arr[20]; // An array of arrays of strings

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

char *vector_arr[20][N]; 

В противном случае, вам придется выделить каждый массив char * динамически:

char **vector_arr[20] = { NULL }; // initialize all the pointers to NULL 

for (i = 0; i < 20; i++) 
{ 
    // the type of the expression vector_arr is 20-element array of char **, so 
    // the type of the expression vector_arr[i] is char **, so 
    // the type of the expression *vector_arr[i] is char *, so 
    // the type of the expression vector[i][j] is char *, so 
    // the type of the expression *vector_arr[i][j] is char 

    vector_arr[i] = malloc(sizeof *vector_arr[i] * num_args_for_this_element); 
    if (vector_arr[i]) 
    { 
    for (j = 0; j < num_args_for_this_element) 
    { 
     vector_arr[i][j] = malloc(sizeof *vector_arr[i][j] * (size_of_this_element + 1)); 
     // assign the argument 
     strcpy(vector_arr[i][j], argument_for_this_element); 
    } 
    } 
} 

Таким образом, каждый Элемент vector_arr представляет собой N-элементный массив указателей на массивы M-элементов от char.

+2

+1 Исключительный ответ. – par

+1

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

Смежные вопросы