#c #function #pointers #c-strings
#c #функция #указатели #c-струны
Вопрос:
Итак, у меня есть пример программы ниже (часть более крупной программы), и мне нужно передать указатель на строку (двойной указатель для символа) в функцию и изменить строку внутри функции. Каков наилучший способ добиться этого?
#include lt;string.hgt; #include lt;stdio.hgt; int incr(char **ptr) { char ar[104]; scanf("%sn",ar); *ptr = ar; // this prints the string correctly printf("%sn",*ptr); return 0; } int main(void) { char *d; // pass the string (char array) to function // expecting the input from scanf to be stored // in this pointer (modified by the function) incr(amp;d); printf("%sn",d); return 0; }
Вывод из valgrind:
$ gcc test.c -o terst $ valgrind --tool=memcheck --leak-check=yes --show-reachable=yes ./terst ==1346438== Memcheck, a memory error detector ==1346438== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al. ==1346438== Using Valgrind-3.16.0 and LibVEX; rerun with -h for copyright info ==1346438== Command: ./terst ==1346438== Sampletexttodisplay Sampletexttodisplay ==1346438== Conditional jump or move depends on uninitialised value(s) ==1346438== at 0x4C38329: strlen (vg_replace_strmem.c:459) ==1346438== by 0x4EB48D5: puts (in /usr/lib64/libc-2.28.so) ==1346438== by 0x400658: main (in prog/terst) ==1346438== ==1346438== Conditional jump or move depends on uninitialised value(s) ==1346438== at 0x4C38338: strlen (vg_replace_strmem.c:459) ==1346438== by 0x4EB48D5: puts (in /usr/lib64/libc-2.28.so) ==1346438== by 0x400658: main (in prog/terst) ==1346438== ==1346438== Conditional jump or move depends on uninitialised value(s) ==1346438== at 0x4EBE86D: _IO_file_xsputn@@GLIBC_2.2.5 (in /usr/lib64/libc-2.28.so) ==1346438== by 0x4EB4992: puts (in /usr/lib64/libc-2.28.so) ==1346438== by 0x400658: main (in prog/terst) ==1346438== ==1346438== Conditional jump or move depends on uninitialised value(s) ==1346438== at 0x4EBE87F: _IO_file_xsputn@@GLIBC_2.2.5 (in /usr/lib64/libc-2.28.so) ==1346438== by 0x4EB4992: puts (in /usr/lib64/libc-2.28.so) ==1346438== by 0x400658: main (in prog/terst) ==1346438== ==1346438== Syscall param write(buf) points to uninitialised byte(s) ==1346438== at 0x4F2F648: write (in /usr/lib64/libc-2.28.so) ==1346438== by 0x4EBE1FC: _IO_file_write@@GLIBC_2.2.5 (in /usr/lib64/libc-2.28.so) ==1346438== by 0x4EBD56E: new_do_write (in /usr/lib64/libc-2.28.so) ==1346438== by 0x4EBF2B8: _IO_do_write@@GLIBC_2.2.5 (in /usr/lib64/libc-2.28.so) ==1346438== by 0x4EBF692: _IO_file_overflow@@GLIBC_2.2.5 (in /usr/lib64/libc-2.28.so) ==1346438== by 0x4EB4A61: puts (in /usr/lib64/libc-2.28.so) ==1346438== by 0x400658: main (in prog/terst) ==1346438== Address 0x5207490 is 16 bytes inside a block of size 1,024 alloc'd ==1346438== at 0x4C34F0B: malloc (vg_replace_malloc.c:307) ==1346438== by 0x4EB260F: _IO_file_doallocate (in /usr/lib64/libc-2.28.so) ==1346438== by 0x4EC04BF: _IO_doallocbuf (in /usr/lib64/libc-2.28.so) ==1346438== by 0x4EBF727: _IO_file_overflow@@GLIBC_2.2.5 (in /usr/lib64/libc-2.28.so) ==1346438== by 0x4EBE8CE: _IO_file_xsputn@@GLIBC_2.2.5 (in /usr/lib64/libc-2.28.so) ==1346438== by 0x4EB4992: puts (in /usr/lib64/libc-2.28.so) ==1346438== by 0x400631: incr (in prog/terst) ==1346438== by 0x40064C: main (prog/terst) ==1346438== )▒▒lay ==1346438== ==1346438== HEAP SUMMARY: ==1346438== in use at exit: 0 bytes in 0 blocks ==1346438== total heap usage: 2 allocs, 2 frees, 2,048 bytes allocated ==1346438== ==1346438== All heap blocks were freed -- no leaks are possible ==1346438== ==1346438== Use --track-origins=yes to see where uninitialised values come from ==1346438== For lists of detected and suppressed errors, rerun with: -s ==1346438== ERROR SUMMARY: 40 errors from 5 contexts (suppressed: 0 from 0) $
Как вы можете видеть, printf в main не выводит ожидаемый вывод «Sampletexttodisplay» (он просто выводит кучу мусора), в то время как printf в функции incr выводит. Итак, что-то происходит, что исходный указатель изменяется, но не на нужную строку. Есть ли быстрое решение для этого или есть какой-то более предпочтительный метод изменения строк в функциях? Спасибо за помощь.
Комментарии:
1. Вы устанавливаете указатель так, чтобы он указывал на локальную переменную, которая будет уничтожена после выхода функции. Вам нужно будет использовать что-то вроде
malloc
выделения некоторой памяти для указания.2. Фраза «лучший способ добиться этого» звучит так, как будто вы задаете субъективный/основанный на мнении вопрос, а также не даете достаточно подробностей. Какого рода ответ вы ищете? Каковы требования к вам, чтобы принять его?
3. Проясняю пару вещей. Указатели не являются массивами. Массивы не являются указателями. Строки-это массивы, а не указатели.
Ответ №1:
d
это уже указатель, вы можете использовать его напрямую. Но сначала вам нужно выделить для него немного памяти malloc()
.
Кроме того, scanf("%sn",d)
не следует использовать, новая строка в конце сделает так, что scanf()
заполнение будет вечно ждать ввода. Введенная новая строка в конце автоматически удаляется при использовании scanf()
. Вместо этого просто используйте scanf("%s",d)
.
Рабочий код:
#include lt;string.hgt; #include lt;stdio.hgt; #include lt;stdlib.hgt; int incr(char *ptr) { scanf("%s",ptr); printf("%sn",ptr); return 0; } int main(void) { char *d = malloc(sizeof(char) * 104); // pass the string (char array) to function // expecting the input from scanf to be stored // in this pointer (modified by the function) incr(d); printf("%sn",d); free(d); return 0; }
Ответ №2:
Как предположил @RetiredNinja в комментариях, проблема заключается в продолжительности жизни массива ar
. Этот массив выделяется в стеке на время вызова incr
. После incr
возврата указатель на это место памяти может работать, но может и не работать.
Если память выделяется динамически, ее срок службы не привязан к вызову функции, и она будет оставаться действительной до тех пор, пока ее не освободят free
.
Вам также рекомендуется указать ширину поля в scanf
.
int incr(char **ptr) { char *ar = malloc(104); // char is 1 byte, so this allocates 104 chars scanf("3s", ar); *ptr = ar; printf("%sn", *ptr); return 0; }
С другой стороны, в этом нет необходимости, и мы можем использовать scanf
для чтения текста непосредственно в ptr
.
int incr(char **ptr) { scanf("3s", *ptr); printf("%sn", *ptr); return 0; }
Это имеет два преимущества. Это позволяет избежать этой функции, динамически распределяющей память, которую мы можем потерять из виду. Это также означает, что любая память, изначально выделенная для ptr
этого, не утекает.
Однако это лишает нас возможности сканировать текст и проверять его перед изменением ptr
.
Ответ №3:
Вам удалось «изменить строку в функции». Ну, на самом деле не изменять, скорее создавать с помощью печати. Вы можете вывести его внутри функции, в то время как массив, в котором вы сохранили строку, все еще действителен и доступен для доступа, т. Е. вплоть до конца выполнения функции.
После этого доступ к уже несуществующей локальной переменной больше не разрешается. Если бы вы этого не сделали, все было бы хорошо — за исключением немного странного API, который позволяет хранить указатель на что-то запрещенное за пределами функции. Ваша конструкция указателя на poniter таким образом очень похожа на возврат запрещенного указателя из функции.
Затем вы получаете доступ к запрещенной, больше не существующей строке. И это, конечно, не удается.
Вы можете либо передать параметр указателя (на мой взгляд, не указатель на указатель, который не нужен) на строку или, по крайней мере, на юридическую память; это должно быть частью определения вашего интерфейса «Уважаемый пользователь, указатель, который вы передаете, должен указывать на допустимую и достаточно большую юридическую память для строки».
Или вы можете придерживаться интерфейса «указатель на указатель» и определить, что указатель впоследствии укажет на что-то подходящее. Для этого вам придется предоставить нам законный способ создания такой правовой памяти, например malloc()
. И ваша спецификация интерфейса должна была бы включать «Уважаемый пользователь, указатель, на который ссылается параметр «указатель на указатель», впоследствии будет указываться в юридической памяти. Если вы вызываете эту функцию, вы несете ответственность за правильное освобождение ее в конце использования».
Ответ №4:
Несколько вещей: Из вашего комментария кажется, что вы
Вы не пытаетесь изменить указатель, вы пытаетесь изменить то, на что он указывает, поэтому вы можете просто передать исходный указатель.
Там, где ваш код терпит неудачу, как указывает отставной Ниндзя в своем комментарии, ваш исходный массив символов уничтожается, когда вы покидаете свою функцию, поскольку это локальная переменная. Чтобы избежать этого, вы должны объявить его в main.
Я написал ниже то, что, по моему мнению, вы намеревались сделать. Здесь мы видим, что происходит следующее:
- Вы создаете пространство для своего массива с помощью char ar[104]. Здесь ar указывает на ваш массив
- Вы передаете ar в свою функцию incr, где ее локальная копия создается и сохраняется как ptr. Но поскольку мы пытаемся изменить не указатель, а то, на что он указывает, все в порядке.
- Данные записываются в то место, на которое указывает ptr, то есть в то же место, на которое указывает наш исходный указатель ar.
#include lt;string.hgt; #include lt;stdio.hgt; int incr(char* ptr) { scanf("%sn",ptr); printf("%sn",ptr); return 0; } int main(void) { char ar[104]; incr(ar); printf("%sn",d); return 0; }