Почему я не могу скопировать массив с помощью `=`? - PullRequest
7 голосов
/ 20 июня 2009

Я начинаю изучать C, читая K & R и выполняя некоторые из упражнений. После некоторых трудностей я наконец смог выполнить упражнение 1-19 с кодом ниже:

/* reverse: reverse the character string s */
void reverse(char s[], int slen)
{
  char tmp[slen];
  int i, j;

  i = 0;
  j = slen - 2;    /* skip '\0' and \n */

  tmp[i] = s[j];
  while (i <= slen) {
    ++i;
    --j;
    tmp[i] = s[j];
  }

  /* code from copy function p 29 */
  i = 0;
  while ((s[i] = tmp[i]) != '\0')
    ++i;

}

Мой вопрос касается последнего бита кода, в который массив tmp char копируется в s. Почему простой s = tmp; не работает вместо этого? Почему нужно перебирать индекс копирования массива по индексу?

Ответы [ 9 ]

20 голосов
/ 21 июня 2009

Может быть, я просто старый и сварливый, но другие ответы, которые я видел, похоже, полностью упускают из виду.

C не выполняет присваивания массивов, точка. Вы не можете назначить один массив другому массиву простым присваиванием, в отличие от некоторых других языков (например, PL / 1; Pascal и многие его потомки - Ada, Modula, Oberon и т. Д.). Также C не имеет строкового типа. Он содержит только массивы символов, и вы не можете копировать массивы символов (больше, чем вы можете копировать массивы любого другого типа) без использования цикла или вызова функции. [Строковые литералы на самом деле не считаются строковым типом.]

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

В моей копии K & R 2nd Edition, упражнение 1-19 просит функцию reverse(s); в моей копии K & R 1st Edition это было упражнение 1-17 вместо 1-19, но был задан тот же вопрос.

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

#include <string.h>
void reverse(char s[])
{
    int i = 0;
    int j = strlen(s) - 1;
    while (i < j)
    {
        char c = s[i];
        s[i++] = s[j];
        s[j--] = c;
    }
}

#ifdef TEST
#include <stdio.h>
int main(void)
{
    char buffer[256];
    while (fgets(buffer, sizeof(buffer), stdin) != 0)
    {
        int len = strlen(buffer);
        if (len == 0)
            break;
        buffer[len-1] = '\0';  /* Zap newline */
        printf("In:  <<%s>>\n", buffer);
        reverse(buffer);
        printf("Out: <<%s>>\n", buffer);
    }
    return(0);
}
#endif /* TEST */

Скомпилируйте это с параметром -DTEST, чтобы включить тестовую программу и не иметь только определенной функции reverse().

С помощью сигнатуры функции, указанной в вопросе, вы не будете вызывать strlen() дважды на строку ввода. Обратите внимание на использование fgets() - даже в тестовых программах использование gets() - плохая идея. Недостатком fgets() по сравнению с gets() является то, что fgets() не удаляет завершающий символ новой строки, где gets(). Плюсы fgets() в том, что вы не получаете переполнения массива, и вы можете сказать, нашла ли программа новую строку или ей не хватило места (или данных), прежде чем встретить новую строку.

8 голосов
/ 20 июня 2009

Ваш массив tmp был объявлен в стеке , поэтому после завершения метода память, используемая для хранения значений, будет освобождена из-за scoping .

.

s = tmp означает, что s должен указывать на ту же ячейку памяти, что и tmp. Это означает, что когда tmp освобожден, s будет по-прежнему указывать на возможную недопустимую, освобожденную ячейку памяти.

Этот тип ошибки называется висячий указатель .

Редактировать: Это не висячий модификатор, как указано в комментариях к этому ответу. Проблема заключается в том, что выражение s = tmp меняет только то, на что указывает параметр, а не фактический массив, который был передан.

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

void reverse(char s[], int slen) {
    int i = 0;        // First char
    int j = slen - 2; // Last char minus \n\0
    char tmp = 0;     // Temp for the value being swapped

    // Iterate over the array from the start until the two indexes collide.
    while(i < j) {
        tmp = s[i];  // Save the eariler char
        s[i] = s[j]; // Replace it with the later char
        s[j] = tmp;  // Place the earlier char in the later char's spot
        i++;         // Move forwards with the early char
        j--;         // Move backwards with the later char
    }
}
4 голосов
/ 20 июня 2009

Поскольку s и tmp являются адресатами памяти. Если вы s = tmp, оба указателя будут указывать на один и тот же массив.

Предположим, что у нас есть

char s[] ="ab"; 

/*
* Only for explanatory purposes.
* 
*/
void foo(char s[]){ 
    char tmp [] = "cd";
    s= tmp;
 }

foo(s);

после s = tmp у вас будет

s[0] : 'c'
s[1] : 'd'
s[2] : '\0'

Несмотря на то, что оба массива имеют одинаковые данные, изменение tmp повлияет на них обоих, поскольку оба массива фактически одинаковы. Они оба содержат данные, которые находятся в одном и том же адресе памяти. Таким образом, изменяя любую позицию массива tmp или уничтожая массив tmp, s будет зависеть таким же образом.

Зацикливая массив, вы перемещаете часть данных с одного адреса памяти на другой.

В моей копии K & R, указатели описаны в главе 4. Может помочь быстрый просмотр первых страниц.

1 голос
/ 21 июня 2009

Чтобы завершить обсуждение здесь, есть два других возможных способа преобразования в виде строки:

void reverse(char string1[], char string2[])
{
  int i = 0, len = 0;

  while(string2[len] != '\0')   // get the length of the string
      len++;

  while(len > 0)
  {
    string1[i] = string2[len-1]; // copy the elements in reverse
    i++;
    len--;
  }
  string1[i] = '\0'; // terminate the copied string 
}

Или рекурсивно:

void reverse (const char *const sPtr)
{
  //if end of string
  if (sPtr[0] == '\0')
  {
    return;
  }
  else  //not end of the string...
   {
    reverse(&sPtr[1]);  //recursive step
    putchar(sPtr[0]);   //display character
   }
}
0 голосов
/ 21 июня 2009

Попробуйте поэкспериментировать и посмотрите, что происходит, когда вы делаете такие вещи:

void modifyArrayValues(char x[], int len)
{
    for (int i = 0; i < len; ++i)
        x[i] = i;
}

void attemptModifyArray(char x[], int len)
{
    char y[10];
    for (int i = 0; i < len; ++i)
        y[i] = i;
    x = y;
}


int main()
{
    int i = 0;
    char x[10];
    for (i = 0; i < 10; ++i)
        x[i] = 0;

    attemptModifyArray(x, 10);
    for (i=0; i < 10; ++i)
        printf("%d\n", x[i]); // x is still all 0's

    modifyArrayValues(x, 10);
    for (i=0; i < 10; ++i)
        printf("%d\n", x[i]); // now x has 0-9 in it
}

Что происходит, когда вы изменяете массив непосредственно в attemptModifyArray, вы просто перезаписываете локальную копию адреса массива x. Когда вы вернетесь, исходный адрес все еще будет в main копии x.

Когда вы изменяете значения в массиве в modifyArrayValues, вы модифицируете сам фактический массив, адрес которого хранится в modifyArrayValues локальной копии x. Когда вы вернетесь, x все еще будет удерживать тот же массив, но вы изменили значения в этом массиве.

0 голосов
/ 20 июня 2009

В этой теме есть интересная дополнительная ветка о массивах и указателях. Я нашел эту ссылку в Википедии со своеобразным фрагментом кода, показывающим, каким может быть «пластилин» C!

/* x designates an array */
x[i] = 1;
*(x + i) = 1;
*(i + x) = 1;
i[x] = 1; /* strange, but correct: i[x] is equivalent to *(i + x) */

Конечно, еще более запутанно в C то, что я могу сделать это:

unsigned int someval = 0xDEADD00D;
char *p = (char *)&someval;

p[2] = (char)0xF0;

Таким образом, взаимозаменяемость указателей и массивов в языке C кажется настолько глубокой, что почти преднамеренной.
Что все остальные думают?

--- Исходное сообщение ---
s и tmp оба являются указателями, поэтому s = tmp просто сделает точку s по адресу, где tmp живет в памяти.
Другая проблема с тем, что вы обрисовали в общих чертах, заключается в том, что tmp является локальной переменной, поэтому она становится неопределенной, когда выходит из области видимости, т.е. когда функция возвращается.

Убедитесь, что вы полностью поняли эти три понятия, и вы не ошибетесь

  1. Область применения
  2. Разница между стеком и кучей
  3. Указатели

Надеюсь, что это помогает и продолжать идти!

0 голосов
/ 20 июня 2009

В случае s = tmp значение tmp, которое также является начальным адресом массива, будет скопировано в s.

Таким образом, s и tmp будут указывать на один и тот же адрес в памяти, что, я думаю, не является целью.

ура

0 голосов
/ 20 июня 2009

Очень прямой ответ - и s, и tmp являются указателями на ячейку памяти, а не на сами массивы. Другими словами, s и tmp - это адреса памяти, в которых хранятся значения массива, но не сами значения. И одним из распространенных способов доступа к этим значениям массива является использование таких индексов, как s [0] или tmp [0].

Теперь, если вы попытаетесь просто скопировать, s = tmp, адрес памяти массива tmp будет скопирован в s. Это означает, что исходный массив s будет потерян, и даже указатель памяти s теперь будет указывать на массив tmp.

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

0 голосов
/ 20 июня 2009

потому что tmp - это указатель, и вам нужно получить копию, а не «ссылку».

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...