Вопрос указателя C

#c #c

#c #c

Вопрос:

Я новичок в указателях в C . Я не уверен, зачем мне нужны указатели типа char * something[20] as, а не просто char something[20][100] . Я понимаю, что второй метод будет означать, что для каждого элемента в массиве будет выделено 100 блоков памяти, но не приведет ли первый метод к проблемам с утечкой памяти.

Если бы кто-нибудь мог объяснить мне, как char * something[20] находит память, это было бы здорово.

Редактировать:

Моя книга C Primer Plus делает:

 const char * cities[5] = {
"City 1",
"City 2",
"City 3",
"City 4",
"City 5"
}
  

Разве это не противоположно тому, что только что сказали люди?

Комментарии:

1. я помню, как в книге «C Primer» это объясняется, в частности

2. Вероятно, вам вообще не нужны указатели. Если вы используете C , ваш, char * something[20] вероятно, должен быть std::vector<std::string> something(20) .

Ответ №1:

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

 something[0] = new char[100];
something[1] = new char[20]; // they can differ in size
  

И удалите их все по отдельности:

 delete [] something[0];
delete [] something[1];
  

Редактировать:

 const char* text[] = {"These", "are", "string", "literals"};
  

Строки, указанные непосредственно в исходном коде («строковые литералы», которые всегда есть const char * ), сильно отличаются от char * , главным образом потому, что вам не нужно беспокоиться о выделении / освобождении из них. Они также обычно обрабатываются по-разному в памяти, но это зависит от реализации вашего компилятора.

Ответ №2:

Вы правы.

  • Вам нужно будет просмотреть каждый элемент этого массива и выделить буфер символов для каждого из них.

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

Почему вы хотели бы возиться с этим в C , можно только догадываться.

Что не так с std::vector<std::string> myStrings(20) ?

Ответ №3:

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

Они не будут инициализированы, поэтому типичное использование выглядит следующим образом

 char * something[20];
for (int i=0; i<20; i  )
    something[i] = strdup("something of a content");
  

и более поздние версии

 for (int i=0; i<20; i  )
    if (something[i]) 
       free(something[i]);
  

Комментарии:

1. (1) вы, конечно, имели в виду something[i] = strdup(...) ? (2) Память, выделенная с помощью strdup , должна быть освобождена с помощью free , никогда delete .

2. Проблема… (1) конечно, сделал (2) Я изменил свое мнение о примере на полпути. Вот как я их перепутал… упс

Ответ №4:

Вы правы — первый метод может привести к проблемам с утечкой памяти и накладным расходам на выполнение динамических распределений, плюс к большему количеству операций чтения. Я думаю, что второй метод обычно предпочтительнее, если только он не расходует слишком много оперативной памяти или вам не может понадобиться, чтобы строки были длиннее 99 символов.

Как работает первый метод:

 char* something[20];  // Stores 20 pointers.
something[0] = malloc(100);  // Make something[0] point to a new buffer of 100 bytes.
sprintf(something[0], "hai");  // Make the new buffer contain "hai", going through the pointer in something[0]
free(something[0]);  // Release the buffer.
  

Ответ №5:

char* smth[20] не выделяет никакой памяти в куче. В стеке выделяется ровно столько места, чтобы хранить 20 указателей. Значение этих указателей не определено, поэтому перед их использованием вы должны инициализировать их, вот так:

 char* smth[20];
smth[0] = new char[100]; // allocate memory for 100 chars, store the address of the first one in smth[0]
//..some code..
delete[] smth[0];
  

Ответ №6:

Прежде всего, это почти неприменимо в C . Нормальным эквивалентом в C было бы что-то вроде: std::vector<std::string> something;

В C основное отличие заключается в том, что вы можете выделить каждую строку отдельно от других. С помощью char something[M][N] вы всегда выделяете точно такое же количество строк и одинаковое пространство для каждой строки. Это часто приводит к пустой трате места (когда строки короче, чем вы выделили для них) и не позволит вам работать с большим количеством строк или более длинными строками, чем вы выделили для них изначально.

char *something[20] давайте разберемся с более длинными / короткими строками более эффективно, но все равно остается место только для 20 строк.

Следующий шаг (если вы чувствуете себя предприимчивым) — использовать что-то вроде:

 char **something;
  

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

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

Ответ №7:

В C есть указатели, потому что в C есть указатели.

Почему мы используем указатели?

  1. Для отслеживания динамически выделяемой памяти. Функции выделения памяти в C ( malloc , calloc realloc ) и new оператор в C возвращают значения указателя.

  2. Для имитации семантики передачи по ссылке (только C).В C все аргументы функции передаются по значению; формальный параметр и фактический параметр являются различными объектами, и изменение формального параметра не влияет на фактический параметр. Мы обходим это, передавая указатели на функцию. В C представлены ссылочные типы, которые служат той же цели, но немного чище и безопаснее, чем использование указателей.

  3. Для построения динамических структур данных, ссылающихся на себя. A struct не может содержать экземпляр самого себя, но он может содержать указатель на экземпляр. Например, следующий код

     
    struct node
    {
      data_t data;
      struct node *next;
    };
      

    создает тип данных для простого узла связанного списка; next элемент явно указывает на следующий элемент в списке. Обратите внимание, что в C все контейнеры STL для стеков, очередей и векторов используют указатели под капотом, изолируя вас от ведения бухгалтерского учета.

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

Ваш массив указателей можно было бы использовать для хранения строк различной длины, выделив для каждой ровно столько памяти, сколько нужно, вместо того, чтобы полагаться на некоторый максимальный размер (который в конечном итоге будет превышен, что приведет к ошибке переполнения буфера и в любом случае приведет к фрагментации внутренней памяти). Естественно, в C вы бы использовали string тип данных (который скрывает весь указатель и управление памятью за class API) вместо указателей на char , но кто-то решил запутать вас, начав с деталей низкого уровня вместо общей картины.

Комментарии:

1. Я думаю, вы пропустили основную причину, или, скорее, спрятали ее под техническим обоснованием. Мы используем указатели для объектов, для которых важна идентификация (поэтому мы не можем копировать), и время жизни которых не соответствует одному из предопределенных сроков жизни (автоматическому, временному или статическому). Конечно, это означает динамическое распределение, о котором вы упомянули, но без этих ограничений нет причин использовать динамическое распределение для начала.

Ответ №8:

Я не уверен, зачем мне нужны указатели типа char * something [20], а не просто char something[20][100]. Я понимаю, что второй метод будет означать, что для каждого элемента в массиве будет выделено 100 блоков памяти, но не приведет ли первый метод к проблемам с утечкой памяти.

Второго метода будет достаточно, если вы ссылаетесь только на свой буфер (ы) локально.

Проблема возникает, когда вы передаете имя массива другой функции. Когда вы переходите char something[10] к другой функции, вы фактически передаете char* something , потому что длина массива не соответствует ожиданиям.

Для многомерных массивов вы можете объявить функцию, которая принимает массив определенной длины во всех направлениях, кроме одного, например foo(char* something[10]) .

Итак, почему используется первая форма, а не вторая? Я могу назвать несколько причин:

  1. Вы не хотите иметь ограничение, согласно которому весь буфер должен находиться в непрерывной памяти.
  2. Во время компиляции вы не знаете, что вам понадобится каждый буфер или что длина каждого буфера должна быть одинакового размера, и вам нужна гибкость, чтобы определить это во время выполнения.
  3. Это объявление функции.

Ответ №9:

символ * что-то [20]

Предполагая, что это 32 бита, это выделяет 80 байт данных в стеке. 4 байта для каждого адреса указателя, всего 20 указателей = 4 x 20 = 80 байт.

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

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

[0] [4 байта неинициализированных данных для хранения адреса указателя / памяти …] [1] [4 байта из … ] … [19]

что-нибудь с символом[20][100]

Выделяет 2000 байт в стеке. 100 байт для каждого чего-то, всего 20 чего-то = 100 x 20 = 2000 байт.

[0] [100 байт для хранения символов] [1] [100 байт для хранения символов] … [19]

Символ *, имеет меньшую нагрузку на память, но вы должны управлять памятью. Подход с использованием символа [][] имеет большие затраты памяти, но у вас нет дополнительного управления памятью.

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