Передача по ссылке в C - минусы? - PullRequest
0 голосов
/ 20 августа 2009

Большинство языков высокого уровня (Python, Ruby, даже Java) используют справочные ссылки. Очевидно, что у нас нет ссылок в C, но мы можем имитировать их, используя указатели. Есть несколько преимуществ для этого. Например:

int findChar(char ch, char* in)
{
    int i = 0;
    for(i = 0; in[i] != '\0'; i++)
        if(in[i] == ch)
            return i;
    return -1;
}

Это обычная парадигма C: поймать ненормальную или ошибочную ситуацию, возвращая некоторое значение ошибки (в этом случае, верните -1, если символ отсутствует в строке).

Проблема в том, что если вы хотите поддерживать строки длиной более 2^31 - 1 символов? Очевидное решение - вернуть unsigned int, но это значение не будет работать с этим значением ошибки.

Решение выглядит примерно так:

unsigned int* findChar(char ch, char* in)
{
    unsigned int i = 0;
    for(i = 0; in[i] != '\0'; i++)
        if(in[i] == ch)
        {
            unsigned int index = (unsigned int*) malloc(sizeof(unsigned int));
            *index = i;
            return index;
        }
    return NULL;
}

Есть некоторые очевидные оптимизации, которые я не сделал для простоты, но вы поняли идею; верните NULL в качестве значения ошибки.

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

Есть ли недостатки этого подхода (помимо использования памяти), который мне не хватает?

РЕДАКТИРОВАТЬ: Я хотел бы добавить (если это не совсем очевидно по моему вопросу), что у меня есть некоторый опыт в C ++, но я в значительной степени начинающий в C.

Ответы [ 7 ]

6 голосов
/ 20 августа 2009

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

Гораздо лучше было бы вернуть указатель на функцию поиска символов или NULL, если ее нет. Вот так strchr() работает, кстати.

Отредактировано для отражения изменений в исходном сообщении.

3 голосов
/ 20 августа 2009

Без malloc позиция может быть переменной стека, и вы можете использовать ее в операторе if:

int findChar(char ch, char* in, int* pos)
{
    int i = 0;
    for(i = 0; in[i] != '\0'; i++) 
    {
        if(in[i] == ch) 
        {
            *pos = i;
            return 1;
        }
    }

    return 0;
}
2 голосов
/ 20 августа 2009

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

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

Существует еще один стандартный подход: errno. В случае индикатора ошибки, вы не знаете, что это за ошибка. Поэтому в C вместо использования параметра out мы обычно помещаем подробности ошибки в глобальную или локальную переменную потока.

1 голос
/ 20 августа 2009

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

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

int findChar(char ch, char const * const in, unsigned int * const index)
{
    if ( in != NULL && index != NULL)
    {
        unsigned int i;
        for(i = 0; in[i]; i++)
        {
            if(in[i] == ch)
            {
                *index = i;
                return EXIT_SUCCESS;
            }
        }
    }
    return EXIT_FAILURE;
}

... где возвращаемое значение функции сообщает вам, была ли функция успешной или нет, отдельно от значения 'index'.

Опять же, как отметил Фортран, нет способа принудительно установить, являются ли указатели входными, выходными или обоими (то есть изменены внутри функции).

1 голос
/ 20 августа 2009

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

1 голос
/ 20 августа 2009

Самым большим недостатком является то, что для освобождения () возвращаемой памяти или создания утечки памяти требуются вызывающие функции findChar (). Вы плохо изобрели колесо strchr ().

Я также не понимаю, почему вы думаете, что возврат указателя на unsigned int - это большой шаг вперед. Во-первых, вы можете просто вернуть unsigned int, если все, что вам нужно, это возможность возвращать значения до 2 ^ 32 на 32-битной машине вместо 2 ^ 31-1. Во-вторых, ваша заявленная цель - избежать проблем с большими строками. Ну, а если вы на 64-битной машине, где int и unsigned int остаются 32 битами? То, что вы действительно хотите здесь, это долго, но возврат указателей здесь на самом деле не помогает.

КРИТИЗМ БОЛЬШОГО БОЖЬЕГО

1 голос
/ 20 августа 2009
  1. Функция должна разыменовать параметры, что занимает больше времени, чем доступ к стеку.
  2. Указатели могут быть неинициализированы, что приведет к неожиданным результатам.
  3. Не существует стандартного способа указать, какой указатель предназначен для ввода, какой для вывода, а какой для обоих (есть расширения и приемы именования, но это все еще остается вопросом).
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...