Я передаю копию моего массива char или указатель? - PullRequest
4 голосов
/ 23 марта 2011

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

По сути, я просто хочу понять, как работает моя функция.Когда я называю его «tempstr» в качестве первого параметра, это следует понимать как адрес первого элемента в массиве?По сути, как сказать & tempstr [0], верно?

Я думаю, ответ на этот вопрос скажет мне: будет ли какая-то разница, если я назначу указатель char * на свой массив tempstr, а затем отправлю его в stringreverse () какпервый парам, а как я сейчас это делаю?Я хочу знать, отправляю ли я копию массива tempstr или адрес памяти.

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

int main()
{
    char* stringreverse(char* tempstr, char* returnptr);

    printf("\nEnter a string:\n\t");

    char tempstr[1024];

    gets(tempstr);
    char *revstr = stringreverse(tempstr, revstr); //Assigns revstr the address of the first character of the reversed string.

    printf("\nReversed string:\n"
           "\t%s\n", revstr);

    main();
    return 0;
}

char* stringreverse(char* tempstr, char* returnptr)
{
    char revstr[1024] = {0};

    int i, j = 0;

    for (i = strlen(tempstr) - 1; i >= 0; i--, j++)
    {
        revstr[j] = tempstr[i]; //string reverse algorithm
    }

    returnptr = &revstr[0];
    return returnptr;
}

Спасибо за ваше время.Любая другая критика будет полезна.,только несколько недель в программировании: P

РЕДАКТИРОВАТЬ: Благодаря всем ответам, я понял это.Вот мое решение для всех, кто интересуется:

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

void stringreverse(char* s);

int main(void)
{
    printf("\nEnter a string:\n\t");

    char userinput[1024] = {0}; //Need to learn how to use malloc() xD

    gets(userinput);
    stringreverse(userinput);

    printf("\nReversed string:\n"
           "\t%s\n", userinput);

    main();
    return 0;
}

void stringreverse(char* s)
{
    int i, j = 0;
    char scopy[1024]; //Update to dynamic buffer
    strcpy(scopy, s);

    for (i = strlen(s) - 1; i >= 0; i--, j++)
    {
        *(s + j) = scopy[i];
    }
}

Ответы [ 5 ]

1 голос
/ 23 марта 2011

В ответ на ваш вопрос о том, является ли объект, передаваемый в функцию, массивом или указателем, соответствующая часть стандарта C99 (6.3.2.1/3) гласит:

За исключением случаев, когда это операнд оператора sizeof или унарный оператор &, или строковый литерал, используемый для инициализации массива, выражение с типом '' массив типа '' преобразуется в выражение с типом ' указатель на тип, который указывает на начальный элемент объекта массива и не является lvalue.

Так что да, кроме введения другой явной переменной, следующие две строки эквивалентны:

char x[] = "abc";                     fn (x);
char x[] = "abc"; char *px = &(x[0]); fn (px);

Что касается критики, я хотел бы поднять следующее.


Хотя это и законно, я считаю неуместным иметь прототипы функций (например, stringreverse) где-либо, кроме уровня файлов. На самом деле, я склоняюсь к тому, чтобы упорядочить свои функции так, чтобы они обычно не были необходимы, что делает на одно место меньше места, где вы должны его изменить, если необходимо изменить аргументы или тип возвращаемого значения. Это влечет за собой, в данном случае, размещение stringreverse перед main.


Не никогда не использовать gets в реальной программе. Он не защищен от переполнения буфера. Как минимум, используйте fgets, который можно защитить, или используйте подходящую функцию ввода, такую ​​как найденная здесь .


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


Также нет необходимости передавать переменную revstr. Если бы это был указатель с резервной памятью (т. Е. Для него было выделено место), это было бы хорошо, но тогда не было бы необходимости возвращать его. В этом случае вы должны выделить оба в вызывающей стороне:

char tempstr[1024];
char revstr[1024];
stringreverse (tempstr, revstr); // Note no return value needed
                                 //  since you're manipulating revstr directly.

Вам также следует избегать магических чисел, таких как 1024. Лучше иметь такие строки, как:

#define BUFFSZ 1024
char tempstr[BUFFSZ];

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


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

char *reversestring (char *src) {
    char *dst = malloc (strlen (src) + 1);
    if (dst != NULL) {
        // copy characters in reverse order.
    }
    return dst;
}

Это возлагает ответственность за освобождение памяти на вызывающего, но это изношенный способ делать вещи.


Вам, вероятно, следует использовать одну из двух канонических форм для main:

int main (int argc, char *argv[]);
int main (void);

Также особенно плохая идея - звонить main из куда-либо. Хотя это может выглядеть как изящный способ получить бесконечный цикл, почти наверняка это закончится жеванием до стека пространства: -)


В общем, это, вероятно, функция, которую я бы изначально написал. Он позволяет пользователю заполнить свой собственный буфер, если он хочет, или указать, что у него его нет, и в этом случае для него будет создан один:

char *revstr (char *src, char *dst) {
    // Cache size in case compiler not smart enough to do so.
    // Then create destination buffer if none provided.

    size_t sz = strlen (src);
    if (dst == NULL) dst = malloc (sz + 1);

    // Assuming buffer available, copy string.

    if (dst != NULL) {
        // Run dst end to start, null terminator first.

        dst += sz; *dst = '\0';

        // Copy character by character until null terminator in src.
        // We end up with dst set to original correct value.

        while (*src != '\0')
            *--dst = *src++;
    }

    // Return reversed string (possibly NULL if malloc failed).

    return dst;
}
1 голос
/ 23 марта 2011

Во-первых, деталь:

int main()
{
    char* stringreverse(char* tempstr, char* returnptr);

Этот прототип должен выходить за пределы main (), например:

char* stringreverse(char* tempstr, char* returnptr);

int main()
{

Что касается вашего основного вопроса: переменная tempstr isсимвол *, то есть адрес символа.Если вы используете индексную нотацию C, такую ​​как tempstr [i], это, по сути, то же самое, что и * (tempstr + i).То же самое относится и к revstr, за исключением того, что в этом случае вы возвращаете адрес блока памяти, который собирается быть забитым, когда массив, на который он указывает, выходит из области видимости.У вас есть правильная идея передать адрес некоторой памяти, в которую нужно записать обратную строку, но вы на самом деле не копируете данные в память, указанную этим блоком.Кроме того, строка:

returnptr = &revstr[0];

Не делает то, что вы думаете.Вы не можете назначить новый указатель на returnptr;если вы действительно хотите изменить returnptr, вам нужно будет передать его адрес, чтобы в параметре был указан char ** returnptr.Но не делайте этого: вместо этого создайте в вашем main () блок, который получит обратную строку и передаст ее адрес в параметре returnptr.Затем используйте этот блок вместо временного, который вы используете сейчас в stringreverse ().

1 голос
/ 23 марта 2011

По сути, я просто хочу понять, как работает моя функция.

Одна из проблем, с которой вы столкнулись, заключается в том, что вы используете revstr без его инициализации или выделения памяти. Это неопределенное поведение , так как вы пишете в память, не принадлежит вам. Может показаться, что это работает, но на самом деле у вас есть ошибка, которая может привести к неожиданным результатам в любое время.

Когда я называю его «tempstr» в качестве первого параметра, следует ли это понимать как адрес первого элемента в массиве? В основном, как сказать & tempstr [0], верно?

Да. Когда массивы передаются в качестве аргументов функции, они рассматриваются как обычные указатели, указывающие на первый элемент в массиве. Нет разницы, если вы присвоили &temp[0] для char* перед передачей его на stringreverser, потому что это то, что компилятор делает для вас в любом случае.

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

1 голос
/ 23 марта 2011

Когда я вызываю его с 'tempstr' в качестве первого параметра, это следует понимать как адрес первого элемента в массиве?По сути, как сказать &tempstr[0], верно?

char tempstr[1024];

tempstr - это массив символов.Когда tempstr передается в функцию, она распадается на указатель, указывающий на первый элемент tempstr.Таким образом, это в основном то же самое, что отправка &tempstr[0].

Будет ли какая-либо разница, если я назначу указатель char * на свой массив tempstr, а затем отправлю его в stringreverse () в качестве первого параметра, по сравнению скак я это делаю сейчас?

Без разницы.Вы могли бы сделать -

char* pointer = tempstr ; // And can pass pointer

char *revstr = stringreverse(tempstr, revstr); 

Первое выражение правой стороны оценивается, и возвращаемое значение назначается revstr.Но что такое revstr, которое передается.Программа должна выделить для нее память.

char revstr[1024] ;
char *retValue = stringreverse(tempstr, revstr) ;
     // ^^^^^^ changed to be different.

Теперь, когда передаются tempstr и revstr, они распадаются на указатели, указывающие на их соответствующие первые индексы.В таком случае, почему это может пойти не так -

revstr = stringreverse(tempstr, revstr) ;

Просто потому, что массивы не являются указателями .char * отличается от char [].Надеюсь, это поможет!

0 голосов
/ 23 марта 2011

В вашей функции stringreverse() вы возвращаете адрес локальной переменной (revstr). Это неопределенное поведение и это очень плохо. Ваша программа может показаться работающей прямо сейчас, но когда-нибудь в будущем она внезапно потерпит неудачу по неясным причинам.

У вас есть два основных варианта:

  1. Пусть stringreverse() выделит память для возвращаемой строки и оставит ее вызывающей стороне для ее освобождения.
  2. Попросите вызывающего заранее выделить место для возвращаемой строки и скажите stringreverse(), где она и насколько она велика.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...