инициализация указателей на символы

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

Я объявил и инициализировал так:

char * p = NULL;

Мне просто интересно, является ли это хорошей практикой. Я использую gcc 4.3.3.


person ant2009    schedule 02.11.2009    source источник
comment
Я предполагаю, что он спрашивает, должен ли он инициализировать эту строку значением NULL (или 0), или не инициализировать ее вообще.   -  person Roberto Aloi    schedule 02.11.2009
comment
Здесь необходима некоторая осторожность, NULL обычно равен 0, но согласно стандарту может быть другим: The macro NULL, defined in any of <clocale>, <cstddef>, <cstdio>, <cstdlib>, <cstring>, <ctime>, or <cwchar>, is an implementation-defined C++ null pointer constant in this International Standard. Я бы определенно придерживался NULL, кроме того, он более ориентирован на типы и более информативен в исходном коде, чем 0.   -  person RedGlyph    schedule 02.11.2009
comment
Значение int 0 при преобразовании в тип указателя всегда приводит к нулевому указателю. С точки зрения переносимости не имеет значения, что вы делаете, не имеет значения, является ли NULL просто 0. Это все еще оставляет более описательный аргумент, по которому мнения расходятся. Встречная претензия сводится к тому, что макрос NULL является слегка негерметичной абстракцией и вводит в заблуждение при неправильном использовании (int i = NULL;).   -  person Steve Jessop    schedule 02.11.2009
comment
@RedGlyph: другой язык; вы цитируете C++, а не C. C даже не имеет заголовков <clocale> и т.д.   -  person MSalters    schedule 02.11.2009
comment
предпочтительные стили: в C: char * c = NULL; в C++: char * c = 0;   -  person stefanB    schedule 04.11.2009


Ответы (12)


Да, это хорошая идея. Google Code Style рекомендует:

  1. Чтобы инициализировать все ваши переменные, даже если они вам сейчас не нужны.
  2. Инициализируйте указатели NULL, int 0 и float 0.0 - просто для лучшей читабельности.

    int i = 0;
    double x = 0.0;
    char* c = NULL;
    
person f0b0s    schedule 02.11.2009
comment
Кстати, это руководство по стилю C++. Это не говорит нам, что (если что) Google говорит о C. - person Steve Jessop; 02.11.2009
comment
@onebyone: Тем не менее, хорошей практикой является инициализация указателей в NULL, а затем установка их в NULL после их освобождения. Это упрощает их тестирование перед разыменованием, особенно в сложном коде. Это верно для C или C++. - person Tim Post♦; 02.11.2009
comment
@onebyone вы правы, я разработчик С++ и видел синтаксис С++ в вопросе =) Но я думаю, что правило о NULL является хорошей практикой и для C. - person f0b0s; 02.11.2009
comment
Первая часть — предпочтительнее всегда инициализировать — приемлема только в C 99 и C++, где объявления могут быть сделаны в любой точке кода. В классическом C89/90 это основное антиправило. Не тратьте свой код на такую ​​ерунду. Вторая часть — использование инициализаторов для конкретных типов — является антиправилом в любом контексте. Это нарушает одно из самых важных правил хорошего программирования: не вводите избыточность без уважительной причины. Значения с плавающей запятой можно безопасно и успешно инициализировать с помощью 0 (в отличие от 0.0). - person AnT; 03.11.2009
comment
Использование 0.0 не улучшает читаемость и вводит избыточную зависимость типа между левой и правой частями инициализации. Использование NULL для указателей может быть допустимо, но во всех других случаях рекомендуется использовать простой 0 для инициализации всего, что может быть инициализировано с его помощью. По-видимому, Google Code Style более произволен, чем я думал. - person AnT; 03.11.2009
comment
Да, и поскольку это руководство по стилю, оно не обязательно общепринято. Я, например, говорю (и знаю, что у других такое же мнение), что я не инициализирую то, что мне не нужно. Я инициализирую их, когда это необходимо, например, непосредственно перед началом цикла. Ошибки будут обнаружены путем размещения правильных утверждений, а не путем инициализации вещей. - person Johannes Schaub - litb; 04.11.2009

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

person David Rodríguez - dribeas    schedule 02.11.2009

Вы не можете хранить строку в указателе.

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

char *mgt_dev_name = NULL;
char data[4200];

/* ... */

mgt_dev_name = data; /* use array */

/* ... */

mgt_dev_name = malloc(4200);
if (mgt_dev_name != NULL) {
    /* use malloc'd space */
    free(mgt_dev_name);
} else {
    /* error: not enough memory */
}
person pmg    schedule 02.11.2009
comment
Я не знаю, об этом ли он спрашивал, но это то, что я хотел знать. В частности, как инициализировать значение NULL, а затем выделить место для строки. - person Colin Keenan; 13.12.2016

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

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

person Jesper    schedule 02.11.2009
comment
На самом деле, он должен инициализировать его значением NULL. Технически, даже указание на память, которая не принадлежит вашей программе, делает ее неправильно сформированной, поэтому, установив для нее значение NULL, вы избежите этого. (И это хорошая практика кодирования) - person GManNickG; 02.11.2009
comment
Но, по моему мнению, более подвержено ошибкам отсутствие инициализации переменных. - person Roberto Aloi; 02.11.2009
comment
@GMan - Но если он инициализирует переменную каким-то другим значением несколькими строками позже, ничего не делая с промежуточной переменной, в этом нет необходимости. - person Jesper; 02.11.2009
comment
Не требуется для поведения во время выполнения, но для создания правильно сформированной программы его следует инициализировать. - person GManNickG; 02.11.2009
comment
даже указание на память, которая не принадлежит вашей программе, делает ее неправильной. Нет, это не так. Следующий код допустим C с определенным поведением: char *c; c = "hello, world";. Так же и следующее: char *c = malloc(13); free(c); /* c points to memory I don't own! */ c = NULL; - person Steve Jessop; 02.11.2009

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

char *name = NULL;

...

name = initial_value;

Я бы изменил это на:

...

char *name = initial_value;

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

Тем не менее, это разрешено только начиная со стандарта c99 (это также допустимо для C++). Чтобы включить функции c99 в gcc, вам нужно сделать следующее:

gcc -std=gnu99

или если вы не хотите, чтобы расширения gcc соответствовали стандарту:

gcc -std=c99
person R Samuel Klatchko    schedule 03.11.2009

Нет, это не очень хорошая практика, если я правильно понял ваш контекст.

Если ваш код на самом деле зависит от mgt_dev_name, имеющего начальное значение нулевого указателя, то, конечно, включение инициализатора в объявление — очень хорошая идея. т.е. если бы вам все равно пришлось это сделать

char *mgt_dev_name;

/* ... and soon after */
mgt_dev_name = NULL;

тогда всегда лучше использовать инициализацию вместо присваивания

char *mgt_dev_name = NULL;

Однако инициализация хороша только тогда, когда вы можете инициализировать свой объект с осмысленным полезным значением. Значение, которое вам действительно нужно. В общем случае это возможно только в языках, допускающих объявления в любой точке кода, хорошими примерами таких языков являются C99 и C++. К тому времени, когда вам понадобится ваш объект, вы обычно уже знаете подходящий инициализатор для этого объекта и поэтому можете легко придумать элегантное объявление с хорошим инициализатором.

С другой стороны, в C89/90 объявления могут быть размещены только в начале блока. В этот момент, в общем случае, у вас не будет значимых инициализаторов для всех ваших объектов. Должны ли вы просто инициализировать их чем-то, чем угодно (например, 0 или NULL), просто чтобы они инициализировались? Нет!!! Никогда не делайте бессмысленных вещей в своем коде. Это ничего не улучшит, что бы вам ни говорили различные «гайды по стилю». На самом деле бессмысленная инициализация может скрывать ошибки в вашем коде, что затрудняет их обнаружение и исправление.

Обратите внимание, что даже в C89/90 всегда полезно стремиться к лучшей локальности объявлений. т.е. известное руководство по хорошей практике гласит: всегда делайте свои переменные настолько локальными, насколько это возможно. Не накапливайте все объявления локальных объектов в самом начале функции, а переместите их в начало наименьшего блока, максимально плотно охватывающего все время жизни объекта. Иногда может быть даже хорошей идеей ввести фиктивный, в противном случае ненужный блок, просто для улучшения локальности объявлений. Следование этой практике поможет вам предоставить хорошие полезные инициализаторы для ваших объектов во многих (если не в большинстве) случаях. Но некоторые объекты останутся неинициализированными в C89/90 только потому, что у вас не будет хорошего инициализатора для них в момент объявления. Не пытайтесь инициализировать их «чем-то» только ради того, чтобы они были инициализированы. Это не принесет абсолютно ничего хорошего и может иметь негативные последствия.

Обратите внимание, что некоторые современные средства разработки (например, MS Visual Studio 2005) перехватывают доступ во время выполнения к неинициализированным переменным в отладочной версии кода. То есть эти инструменты могут помочь вам обнаружить ситуации, когда вы обращаетесь к переменной до того, как она успела получить значимое значение, указывающее на ошибку в коде. Но выполняя безусловную преждевременную инициализацию ваших переменных, вы, по сути, убиваете эту возможность инструмента и заметаете эти ошибки под ковер.

person AnT    schedule 03.11.2009

Эта тема уже обсуждалась здесь:

http://www.velocityreviews.com/forums/t282290-how-to-initialize-a-char.html

Он относится к C++, но может быть полезен и вам.

person Roberto Aloi    schedule 02.11.2009

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

Да, хорошей практикой является инициализация указателей в NULL, а также установка указателей в NULL после того, как они больше не нужны (т.е. освобождены).

В любом случае очень практично иметь возможность проверить указатель до его разыменования. Допустим, у вас есть структура, которая выглядит так:

struct foo {
    int counter;
    unsigned char ch;
    char *context;
};

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

Поток A блокирует foo, увеличивает счетчик и проверяет значение в ch. Он не находит его, поэтому не выделяет (или не изменяет) контекст. Вместо этого он сохраняет значение в ch, чтобы вместо этого эту работу мог выполнить поток B.

Поток B видит, что счетчик был увеличен, отмечает значение в ch, но не уверен, сделал ли что-нибудь поток A с контекстом. Если контекст был инициализирован как NULL, поток B больше не должен заботиться о том, что сделал поток A, он знает, что контекст безопасен для разыменования (если не NULL) или выделения (если NULL) без утечки.

Поток B делает свое дело, поток A читает его контекст, освобождает его, а затем повторно инициализирует его значением NULL.

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

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

Единственный раз, когда я когда-либо видел «уродливый» случай использования инициализированного указателя (до и после использования), это примерно так:

void my_free(void **p)
{
    if (*p != NULL) {
        free(*p);
        *p = NULL;
    }
}

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

Вероятно, гораздо больше информации, чем вы на самом деле хотели.

person Tim Post♦    schedule 02.11.2009

Предпочтительные стили:

in C: char * c = NULL;

in C++: char * c = 0;

person stefanB    schedule 04.11.2009

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

person Dervin Thunk    schedule 09.06.2010

Инициализация переменных, даже если они вам не нужны сразу, является хорошей практикой. Обычно мы инициализируем указатели значением NULL, int — значением 0, а число с плавающей запятой — значением 0.0 по соглашению.

int* ptr = NULL;
int i = 0;
float r = 0.0;
person Madhav Datt    schedule 03.01.2016

Всегда полезно инициализировать переменные-указатели в C++, как показано ниже:

int *iPtr = nullptr;
char *cPtr = nullptr;

Поскольку инициализация, как указано выше, поможет в условиях, подобных приведенным ниже, поскольку nullptr преобразуется в bool, иначе ваш код в конечном итоге выдаст некоторые предупреждения компиляции или неопределенное поведение:

if(iPtr){
   //then do something.
}

if(cPtr){
  //then do something.
}
person pkthapa    schedule 03.05.2019