|
Программирование >> Арифметические и логические операции
Как мне равноправно использовать статически и динамически задаваемые многомерные массивы при передаче их в качестве параметров функциям? Идеального решения не существует. Возьмем объявления int array[NROWS][NCOLUMNS]; int **array1; int **array2; int *array3; int (*array4)[NCOLUMNS]; соответствующие способам выделения памяти и функции, объявленные как: f1(int a[][NCOLUMNS], int m, int n); f2(int *aryp, int nrows, int ncolumns); f3(int **pp, int m, int n); Тогда следующие вызовы должны работать так, как ожидается: f1(array, NROWS, NCOLUMNS); f1(array4, nrows, NCOLUMNS); f2(&array[0][0], NROWS, NCOLUMNS); f2(*array2, nrows, ncolumns); f2(array3, nrows, ncolumns); f2(*array4, nrows, NCOLUMNS); f3(array1, nrows, ncolumns); f3(array2, nrows, ncolumns); Следующие два вызова, возможно, будут работать, но они включают сомнительные приведения типов, и работают лишь в том случае, когда динамически задаваемое число столбцов ncolumns совпадает с NCOLUMS: f1((int (*)[NCOLUMNS])(*array2), nrows, ncolumns); f1((int (*)[NCOLUMNS])array3, nrows, ncolumns); Необходимо еще раз отметить, что передача &array[0][0] функции f2 не совсем соответствует стандарту. Если вы способны понять, почему все вышеперечисленные вызовы работают и написаны именно так, а не иначе, и если вы понимаете, почему сочетания, не попавшие в список, работать не будут, то у вас очень хорошее понимание массивов и указателей (и нескольких других областей) Си. Вот изящный трюк: если я пишу int realarray(IO); int *array = &realar-ray(-1);, то теперь можно рассматривать array как массив, у которого индекс первого элемента равен единице Хотя этот прием внешне привлекателен, он не удовлетворяет стандартам Си. Арифметические действия над указателями определены лишь тогда, когда указатель ссылается на выделенный блок памяти или на воображаемый завершающий элемент, следующий сразу за блоком. В противном случае поведение программы не определено, даже если указатель не переназначается. Код, приведенный выше, плох тем, что при уменьшении смещения может быть получен неверный адрес (возможно, из-за циклического перехода адреса при пересечении границы сегмента). У меня определен указатель на char, который указывает еще и на int, причем мне необходимо переходить к следующему элементу типа int. Почему ((int *)p)++; не работает? В языке С оператор преобразования типа не означает будем действовать так, как будто эти биты имеют другой тип ; это оператор, который действительно выполняет преобразования, причем по определению получается значение типа rvalue, которому нельзя присвоить новое значение и к которому не применим оператор ++. (Следует считать аномалией то, что компиляторы pcc и расширения gcc вообще воспринимают выражения приведенного выше типа.) Скажите то, что думаете: p = (char *)((int *)p + 1); или просто p += sizeof(int); Могу я использовать void **, чтобы передать функции по ссылке обобщенный указатель? Стандартного решения не существует, поскольку в С нет общего типа указатель-на-указатель. void * выступает в роли обобщенного указателя только потому, что автоматически осуществляются преобразования в ту и другую сторону, когда встречаются разные типы указателей. Эти преобразования не могут быть выполнены (истинный тип указателя неизвестен), если осуществляется попытка косвенной адресации, когда void ** указывает на что-то отличное от void *. Почему не работает фрагмент кода: char *answer; printf( Type some-thing:\n ); gets(answer); printf( You typed \ %s\ \n , answer); Указатель answer , который передается функции gets как место, в котором должны храниться вводимые символы, не инициализирован, т.е. не указывает на какое-то выделенное место. Иными словами, нельзя сказать, на что указывает answer . (Так как локальные переменные не инициализируются, они вначале обычно содержат мусор , то есть даже не гарантируется, что в начале answer - это нулевой указатель. Простейший способ исправить программу - использовать локальный массив вместо указателя, предоставив компилятору заботу о выделении памяти: #include <string.h> char answer[100], *p; printf( Type something:\n ); fgets(answer, sizeof(answer), stdin); if((p = strchr(answer, \n)) != NULL) *p = \0; printf( You typed \ %s\ \n , answer); Заметьте, что в этом примере используется fgets() вместо gets(), что позволяет указать размер массива, так что выход за пределы массива, когда пользователь введет слишком длинную строку, становится невозможным. (К сожалению, fgets() не удаляет автоматически завершающий символ конца строки \n, как это делает gets()). Для выделения памяти можно также использовать malloc(). Не могу заставить работать strcat. В моей программе char *s1 = Hello, ; char *s2 = world! ; char *s3 = strcat(s1, s2); но результаты весьма странные Проблема снова состоит в том, что не выделено место для результата объединения. С не поддерживает автоматически переменные типа string. Компиляторы С выделяют память только под объекты, явно указанные в исходном тексте (в случае стрингов это может быть массив литер или символы, заключенные в двойные кавычки). Программист должен сам позаботиться о том, чтобы была выделена память для результата, который получается в процессе выполнения программы, например результата объединения строк. Обычно это достигается объявлением массива или вызовом malloc. Функция strcat не выделяет память; вторая строка присоединяется к первой. Следовательно, одно из исправлений - в задании первой строки в виде массива достаточной длины: char s1[20] = Hello, ; Так как strcat возвращает указатель на первую строку (в нашем случае s1), переменная s3 - лишняя. В справочнике о функции strcat сказано, что она использует в качестве аргументов два указателя на char. Откуда мне знать о выделении памяти? Как правило, при использовании указателей всегда необходимо иметь в виду выделение памяти, по крайней мере, быть уверенным, что компилятор делает это для вас. Если в документации на библиотечную функцию явно ничего не сказано о выделении памяти, то обычно это проблема вызывающей функции. Краткое описание функции в верхней части страницы справочника в стиле UNIX может ввести в заблуждение. Приведенные там фрагменты кода ближе к определению, необходимому для разработчика функции, чем для того, кто будет эту функцию вызывать. В частности, многие функции, имеющие в качестве параметров указатели (на структуры или стринги, например), обычно вызываются с параметрами, равными адресам каких-то уже существующих объектов (структур или массивов). Другой распространенный пример - функция stat(). Предполагается, что функция, которую я использую, возвращает строку, но после возврата в вызывающую функцию эта строка содержит мусор Убедитесь, что правильно выделена область памяти, указатель на которую возвращает ваша функция. Функция должна возвращать указатель на статически выделенную область памяти или на буфер, передаваемый функции в качестве параметра, или на память, выделенную с помощью malloc(), но не на локальный (auto) массив. Другими словами, никогда не делайте ничего похожего на: char *f() char buf[10]; /* ... */ return buf; Приведем одну поправку (непригодную в случае, когда f() вызывается рекурсивно, или когда одновременно нужны несколько возвращаемых значений): static char buf[10]; Почему в некоторых исходных текстах значения, возвращаемые malloc(), аккуратно преобразуются в указатели на выделяемый тип памяти? До того, как стандарт ANSI/ISO ввел обобщенный тип указателя void *, эти преобразования были обычно необходимы для подавления предупреждений компилятора о приравнивании указателей разных типов. (Согласно стандарту C ANSI/ISO, такие преобразования типов указателей не требуются). Можно использовать содержимое динамически выделяемой памяти после того как она освобождена? Нет. Иногда в старых описаниях malloc() говорилось, что содержимое освобожденной памяти остается неизменным ; такого рода поспешная гарантия никогда не была универсальной и не требуется стандартом ANSI. Немногие программисты стали бы нарочно использовать содержимое освобожденной памяти, но это легко сделать нечаянно. Рассмотрите следующий (корректный) фрагмент программы, в котором освобождается память, занятая односвязным списком: struct list *listp, *nextp; for(listp = base; listp != NULL; listp = nextp) { nextp = listp->next; free((char *)listp); и подумайте, что получится, если будет использовано на первый взгляд более очевидное выражение для тела цикла: listp = listp->next без временного указателя nextp. Откуда free() знает, сколько байт освобождать? Функции malloc/free запоминают размер каждого выделяемого и возвращаемого блока, так что не нужно напоминать размер освобождаемого блока. А могу я узнать действительный размер выделяемого блока? Нет универсального ответа. Я выделяю память для структур, которые содержат указатели на другие динамически создаваемые объекты. Когда я освобождаю память, занятую структурой, должен ли я сначала освободить память, занятую подчиненным объектом? Да. В общем, необходимо сделать так, чтобы каждый указатель, возвращаемый malloc() был передан free() точно один раз (если память освобождается). В моей программе сначала с помощью malloc() выделяется память, а затем большое количество памяти освобождается с помощью free(), но количество занятой памяти (так сообщает команда операционной системы) не уменьшается Большинство реализаций malloc/free не возвращают освобожденную память операционной системе (если таковая имеется), а просто делают освобожденную память доступной для будущих вызовов malloc() в рамках того же процесса. Должен ли я освобождать выделенную память перед возвратом в операционную систему? Делать это не обязательно. Настоящая операционная система восстанавливает состояние памяти по окончании работы программы. Тем не менее, о некоторых персональных компьютерах известно, что они ненадежны при восстановлении памяти, а из стандарта ANSI/ISO можно лишь получить указание, что эти вопросы относятся к качеству реализации . Правильно ли использовать нулевой указатель в качестве первого аргумента функции realloc()? Зачем это нужно? Это разрешено стандартом ANSI C (можно также использовать realloc(...,0) для освобождения памяти), но некоторые ранние реализации С это не поддерживают, и мобильность в этом случае не гарантируется. Передача нулевого указателя realloc() может упростить написание самостартующего алгоритма пошагового выделения памяти. В чем разница между calloc и malloc? Получатся ли в результате применения calloc корректные значения нулевых указателей и чисел с плавающей точкой? Освобождает ли free память, выделенную calloc, или нужно использовать cfree? По существу calloc(m,n) эквивалентна: p = malloc(m * n); memset(p, 0, m * n); Заполнение нулями означает зануление всех битов, и, следовательно, не гарантирует нулевых значений для указателей и для чисел с плавающей точкой. Функция free может (и должна) использоваться для освобождения памяти, выделенной calloc. Что такое alloca и почему использование этой функции обескураживает? alloca выделяет память, которая автоматически освобождается, когда происходит возврат из функции, в которой вызывалась alloca. То
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |