2009-08-20 2 views
0

Большинство языков высокого уровня (Python, Ruby, даже Java) используют ссылку pass-by. Очевидно, что у нас нет ссылок на C, но мы можем имитировать их с помощью указателей. Для этого есть несколько преимуществ. Например:Передача по ссылке в 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; 
} 

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

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

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

EDIT: Я хотел бы добавить (если это не совсем очевидно, мой вопрос), что у меня есть некоторый опыт в C++, но я довольно много начинающий на C.

+10

Java * не * использование проходить по ссылке. Он использует pass by value, но для ссылочных типов передаваемое значение является ссылкой. Между этим и «реальным» переходом по ссылке существует большая разница. Python и Ruby могут быть одинаковыми - я не уверен. ИМО стоит быть очень точным с терминологией здесь. –

+0

@Jon Skeet: да, значения в Python и Ruby работают так же, как ссылки на Java, но терминология отличается в этих сообществах. – newacct

+0

@ Jon Skeet: Это один из способов думать об этом; Я склонен думать об этом, поскольку Java является передачей по ссылке, за исключением примитивов, которая выходит на одно и то же в разных словах. Каждому свое. :) – Imagist

ответ

6

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

Намного лучше было бы вернуть указатель на функции функции char, или NULL, если его нет. Вот так работает strchr(), BTW.

Отредактировано, чтобы отразить изменения в исходном посте.

+0

Это было мое намерение, оно было исправлено. – Imagist

+0

Любая функция полезности, подобная этой, которая была не реентерабельной, - это несчастный случай, ожидающий своего появления. Мне действительно не нравится предложение «static int»! – Roddy

+0

+1 для остальных, хотя! – Roddy

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

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

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

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

+0

Спасибо за ваш вклад. Я знал о 'size_t', но приведенный выше код - всего лишь пример, который я собрал вместе, чтобы показать, о чем я говорю, поэтому я не рассматривал его внимательно. Предполагая, что вы имеете в виду максимальное значение 'size_t' by' (size_t) ', что делать, если вы хотите поддерживать длину строк до максимума? Я понимаю, что это не часто вызывает беспокойство, но поскольку причина, по которой я использую C, заключается в том, чтобы написать интерпретатор для языка более высокого уровня, это все еще вызывает беспокойство. – Imagist

+0

By (size_t) -1, я имел в виду значение -1, отлитое от size_t; и да, это дает вам максимальное значение size_t. Как я уже сказал, вам действительно не нужно поддерживать строки такого размера, поскольку они не могут вписаться в память. Например, на 32-битной машине (size_t) -1 - один байт, не превышающий 4 ГБ; наряду с завершением 0 строка потребует 4 ГБ. Добавьте к этому заголовок malloc, и он просто не поместится в адресное пространство (не говоря уже о том, что вам нужно место для кода, и что операционные системы часто не дают вам полного 4 ГБ). –

1

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

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

Опущенные BOGUS КРИТИКА

+0

Ваша последняя критика для меня не имеет смысла. Насколько я знаю, разница между 'NULL' и' & i', где '* i' равна' 0'. – Imagist

+0

Извините, не думая прямо. Это 2 часа ночи? :) –

+0

ОТПРАВЛЯЕТ ВАШ ВХОД! – Imagist

3

Без таНос, позиция может быть еще переменная стека, и вы можете использовать его в если заявление:

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; 
} 
+0

Это findchar - намного лучший API, чем «int findChar (char ch, char * in)». Хотя не редкость смешивать значение и индикацию ошибки в возвращаемом значении, это плохая практика, поскольку это нарушение KISS. – hlovdal

+0

См. Http://lcsd05.cs.tamu.edu/slides/keynote.pdf и http://video.google.com/videoplay?docid=-3733345136856180693 о том, как писать хорошие API. Читайте о realloc в «Code Complete» Стив Макконнелл на примере ужасного API. – hlovdal

1

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

1

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

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

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; 
} 

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

И снова, как отметил fortran, нет никакого способа обеспечить, являются ли указатели входными значениями, выходными значениями или обоими (т. Е. Измененными внутри функции).

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