В чем причина инициализации или присвоения пустого строкового литерала указателю на char в C или указателю на const char в C ++? - PullRequest
2 голосов
/ 18 февраля 2020

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

В чем причина инициализации или назначения пустого строкового литерала указатель на char в C или указатель на const char в C ++?

Как например:

char* p = "";

или

char* p;
p = "";

Или, как предлагается в комментариях, в C ++:

const char* p = "";

или

const char* p;
p = "";

Я часто вижу эту технику, но не понимаю, почему кто-то должен присвоить пустой строковый литерал указателю char (или указателю на const char для C ++) или откуда он поступает.


Я прочитал Инициализировать строку в C в пустую строку , но этот вопрос направлен на инициализацию массива char пустой строкой.

И я также прочитал: Инициализация строки с пустой строкой , но этот вопрос охватывает неправильный подход к присвоению пустой строки указателю, ранее возвращенному malloc() функция.

Ответы [ 5 ]

4 голосов
/ 18 февраля 2020

В чем причина инициализации или присвоения пустого строкового литерала указателю на char в C или указателю на const char в C ++?

Мне кажется, что здесь есть недоразумение. Указатель не инициализирован пустой строкой. Он инициализируется , указывающим на пустую строку (строковый литерал, который компилятор поместил где-то в памяти). Это существенное различие.

Рассмотрим этот код:

char* p = "";
printf("%p\n", (void*)p);
p = "test";
printf("%p\n", (void*)p);

Возможный вывод:

0x563e72497007
0x563e72497008

В этих случаях p содержит адрес памяти, на котором размещен компилятор два строковых литерала (то есть "" в 0x563e72497007 и "test" в 0x563e72497008). Итак, в этой памяти у вас есть:

0x563e72497007 '\0'                  (i.e. the empty string that only consists 
                                           a string termination character`)

0x563e72497008 't' 'e' 's' 't' '\0'  (i.e. the string "test")

Итак, еще раз - p не инициализируется / не присваивается со строками, он инициализируется / присваивается указывать на строки .

Почему вы хотите инициализировать указатель для указания на пустую строку?

Ну, это как любая другая переменная, которую вы инициализируете ... Вы делаете это, потому что хотите, чтобы переменная имела известное значение в случае, если он используется до того, как ему будет присвоено какое-либо другое назначение. Так же как и int i = 0;.

Другими словами - вы делаете char* p = "";, чтобы убедиться, что p указывает на допустимую строку C -тиля.

Очень просто Пример:

char* p = "";
if (answerIsWrong())
{
    p = "not";
}
printf("The answer is %s correct\n", p);

В зависимости от возвращаемого значения из функции answerIsWrong() это может вывести:

The answer is correct

или

The answer is not correct

В первом случае это Важно, что p был инициализирован до , указывает на пустую строку.

Однако, если вы знаете , вы никогда не будете использовать p до того, как ему будет назначен новое значение, очевидно, нет причин для его инициализации! Тем не менее, некоторые программисты предпочитают всегда инициализировать все переменные - даже если они назначают другое значение перед использованием.

Пример:

char* p = "";  // No reason for this initialization
               // p will be assigned another value before it's used

if (answerIsWrong())
{
    p = " not ";
}
else
{
    p = " absolutely ";
}
printf("The answer is %s correct\n", p);
4 голосов
/ 18 февраля 2020

Это полностью зависит от того, что следует. Например, было бы приемлемо следующее:

const char *p = "";

if (f()) {
   p = "Foo";
}
else if (g())
   p = "Bar";
}

strcat(msg, p);

Тем не менее, это маловероятный сценарий. Значение, впоследствии присвоенное p, вероятно, является указателем на динамически выделенную память, которая требует освобождения, но "" не может быть освобождена, поэтому вы можете получить

char *p = "";
int free_it = 0;

if (f()) {
   p = ff();
   free_it = 1;
}
else if (g())
   p = gg();
   free_it = 1;
}

strcat(msg, p);
if (free_it)
   free(p);

, когда сможете так же легко

char *p = NULL;

if (f()) {
   p = ff();
}
else if (g())
   p = gg();
}

if (p)
   strcat(msg, p);

free(p);

Подход, использованный в вопросе , который вы связали в комментариях , приводит к невозможности освободить значения. Было бы разумнее иметь распределитель узлов и деструктор узла.

int Node_init(Node *node, const char *value) {
    char *value_ = strdup(value);
    if (!value_)
       return 0;

    node->value = value_;
    node->sibling = NULL;
    node->child = NULL;
    return 1;
}

Node *Node_new(const char *value) {
   Node *node = malloc(sizeof(Node));
   if (!node) {
      return NULL;
   }

   if (!Node_init(node, value)) {
      free(node);
      return NULL;
   }

   return node;
}

void Node_destroy(Node *node) {
    free(node->value);
}

void Node_delete(Node *node) {
   Node_destroy(node);
   free(node);
}

int main(void) {
   Node root;
   Node_init(&root, "");
   ...
   Node_destroy(&root);
}
2 голосов
/ 18 февраля 2020

Я бы не стал обязательно go говорить о том, что это какая-то «техника кодирования». Скорее, я думаю, что во многих случаях это просто лучше, чем альтернативы.

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

const char* s;
if(some_condition) {
  s = something();
} else if(some_other_condition) {
  s = something_else();
}
for(const char* p = s; *p; ++p) {
  /* do something */
}

Теперь предположим, что вы с 95% уверенностью знаете, что либо some_condition, либо some_other_condition будет всегда верно, но этот вид кода все еще выглядит страшно для вас (это для меня).

Если вы вообще не инициализируете s и ни одно из условий не выполняется, поведение вашей программы не определено. Это может сделать sh, а может и нет. Позже вы никогда не сможете проверить состояние ошибки, поскольку s может быть буквально чем угодно.

Если вы инициализируете s с помощью NULL, теперь вы можете проверить состояние ошибки, но ваш for l oop все еще содержит UB.

Очевидно, что самым безопасным способом было бы включить что-то вроде else { assert(0); } и явно проверить наличие ошибки, но если в вашей ситуации это не требуется, вы можете инициализировать s до "", и код ничего не сделает, если some_condition и some_other_condition оба будут ложными.

1 голос
/ 18 февраля 2020
const char *p = "";

Возвращает допустимую строку с нулевым символом в конце (представляющую пустую строку). Следовательно, он может использоваться в функциях, принимающих c стиль строки и т. Д. c.

Это отличается от, например: не является допустимой строкой и потерпит неудачу в большинстве функций, принимающих строки c (например, std::string(nullptr) даст UB, скорее всего cra sh).

Я бы не назвал это методика программирования.

1 голос
/ 18 февраля 2020

Инициализация указателя на символ с пустым строковым литералом имеет то преимущество, что на самом деле пустой строковый литерал не является «пустым». Если вы создадите фиктивную программу и посмотрите на char* p = ""; с помощью отладчика, вы увидите, что создан массив char длиной 1, содержащий \0. Это означает, что p указывает на допустимую строку с нулем в конце. Таким образом, вы можете передавать p большинству функций, работающих со строками с нулевым символом в конце (например, в основном со всеми стандартными функциями манипуляции со строками библиотеки), не беспокоясь об их ошибках / ошибках памяти и т. Д. c. Это полезно, например, в случае, когда вы присваиваете некоторому значению значение p, которое зависит от некоторого условия, которое может завершиться ошибкой, оставляя вас с потенциально неопределенным поведением, если вы не инициализировали указатель с правильным значением.

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

...