mujer - Confusión de cuerdas C
scanf string c (7)
Estoy aprendiendo C en este momento y me confundí un poco con matrices de caracteres: cadenas.
char name[15]="Fortran";
No hay problema con esto: es una matriz que puede contener (hasta?) 15 caracteres
char name[]="Fortran";
C cuenta la cantidad de caracteres para mí, así que no tengo que hacerlo: ¡limpio!
char* name;
Bueno. ¿Ahora que? Lo único que sé es que esto puede contener una gran cantidad de caracteres que se asignan más adelante (por ejemplo, a través de la entrada del usuario), pero
- ¿Por qué llaman a esto un indicador de char? Sé de punteros como referencias a variables
- ¿Es esta una "excusa"? ¿Encuentra esto otro uso que en char *?
- ¿Qué es esto en realidad? ¿Es un puntero? ¿Cómo lo usas correctamente?
gracias de antemano, lamas
Creo que esto se puede explicar de esta manera, ya que una imagen vale más que mil palabras ...
Comenzaremos con el char name[] = "Fortran"
, que es un conjunto de caracteres, la longitud se conoce en el momento de la compilación, 7 es exacta, ¿verdad? ¡Incorrecto! es 8, ya que un ''/ 0'' es un carácter de terminación nula, todas las cadenas deben tener eso.
char name[] = "Fortran"; +======+ +-+-+-+-+-+-+-+--+ |0x1234| |F|o|r|t|r|a|n|/0| +======+ +-+-+-+-+-+-+-+--+
En el momento del enlace, el compilador y el vinculador asignaron al name
del símbolo una dirección de memoria de 0x1234. Usando el operador de subíndices, es decir, el name[1]
por ejemplo, el compilador sabe cómo calcular en qué punto de la memoria está el carácter en offset, 0x1234 + 1 = 0x1235, y de hecho es ''o''. Eso es bastante simple, además, con el estándar ANSI C, el tamaño de un tipo de datos char
es de 1 byte, lo que puede explicar cómo el tiempo de ejecución puede obtener el valor de este name[cnt++]
semántico name[cnt++]
, asumiendo que cnt
es un int
eger y tiene por ejemplo, un valor de 3, el tiempo de ejecución se incrementa en uno automáticamente y, contando desde cero, el valor del desplazamiento es ''t''. Esto es simple hasta ahora tan bueno.
¿Qué pasa si se ejecutó el name[12]
? Bueno, el código se bloqueará, o obtendrás basura, ya que el límite de la matriz es del índice / desplazamiento 0 (0x1234) hasta 8 (0x123B). Cualquier cosa después de eso no pertenece a la variable de name
, ¡eso se llamaría un desbordamiento de búfer!
La dirección del name
en la memoria es 0x1234, como en el ejemplo, si tuviera que hacer esto:
printf("The address of name is %p/n", &name); Output would be: The address of name is 0x00001234
En aras de la brevedad y de acuerdo con el ejemplo, las direcciones de memoria son de 32 bits, por lo que se ven los 0 adicionales. ¿Lo suficientemente justo? Bien, sigamos adelante.
Ahora a los punteros ... char *name
es un puntero al tipo de char
....
Edit: Y lo inicializamos a NULL como se muestra. Gracias Dan por señalar el pequeño error ...
char *name = (char*)NULL; +======+ +======+ |0x5678| -> |0x0000| -> NULL +======+ +======+
En el tiempo de compilación / enlace, el name
no apunta a nada, pero tiene una dirección de tiempo de compilación / enlace para el name
del símbolo (0x5678), de hecho es NULL
, la dirección del puntero del name
es desconocida, por lo tanto, 0x0000.
Ahora, recuerde , esto es crucial, la dirección del símbolo se conoce en tiempo de compilación / enlace, pero la dirección del puntero es desconocida, cuando se trata de punteros de cualquier tipo
Supongamos que hacemos esto:
name = (char *)malloc((20 * sizeof(char)) + 1); strcpy(name, "Fortran");
Llamamos a malloc
para asignar un bloque de memoria para 20 bytes, no, no es 21, la razón por la que agregué 1 en el tamaño es para el carácter de terminación nula ''/ 0''. Supongamos que en el tiempo de ejecución, la dirección dada era 0x9876,
char *name; +======+ +======+ +-+-+-+-+-+-+-+--+ |0x5678| -> |0x9876| -> |F|o|r|t|r|a|n|/0| +======+ +======+ +-+-+-+-+-+-+-+--+
Así que cuando haces esto:
printf("The address of name is %p/n", name); printf("The address of name is %p/n", &name); Output would be: The address of name is 0x00005678 The address of name is 0x00009876
Ahora, aquí es donde entra en juego la ilusión de que ''los arrays y los punteros son lo mismo ''
Cuando hacemos esto:
char ch = name[1];
Lo que pasa en tiempo de ejecución es esto:
- Se busca la dirección del
name
del símbolo. - Busca la dirección de memoria de ese símbolo, es decir, 0x5678.
- En esa dirección, contiene otra dirección, una dirección de puntero a la memoria y recuperarla, es decir, 0x9876
- Obtenga el desplazamiento basado en el valor del subíndice de 1 y agréguelo a la dirección del puntero, es decir, 0x9877 para recuperar el valor en esa dirección de memoria, es decir, ''o'' y se asigna a
ch
.
Lo anterior es crucial para comprender esta distinción, la diferencia entre matrices y punteros es cómo el tiempo de ejecución recupera los datos, con punteros, hay una indirección adicional de búsqueda.
Recuerde , una matriz de tipo T siempre se descompondrá en un puntero del primer elemento de tipo T.
Cuando hacemos esto:
char ch = *(name + 5);
- Se busca la dirección del
name
del símbolo. - Busca la dirección de memoria de ese símbolo, es decir, 0x5678.
- En esa dirección, contiene otra dirección, una dirección de puntero a la memoria y recuperarla, es decir, 0x9876
- Obtenga el desplazamiento basado en el valor de 5 y añádalo a la dirección del puntero, es decir, 0x987A para recuperar el valor en esa dirección de memoria, es decir, "r" y se asigna a
ch
.
Por cierto, también puedes hacer eso a la matriz de caracteres también ...
Además, al utilizar operadores de subíndices en el contexto de una matriz, es decir, char name[] = "...";
y el name[subscript_value]
es en realidad el mismo que * (name + subscript_value). es decir
name[3] is the same as *(name + 3)
Y dado que la expresión *(name + subscript_value)
es conmutativa , es decir, a la inversa,
*(subscript_value + name) is the same as *(name + subscript_value)
Por lo tanto, esto explica por qué en una de las respuestas anteriores puede escribirlo así (a pesar de ello, la práctica no se recomienda a pesar de que es bastante legítima )
3[name]
Ok, ¿cómo obtengo el valor del puntero? Para eso se usa el *
. Suponga que el name
del puntero tiene esa dirección de memoria de puntero de 0x9878, nuevamente, refiriéndose al ejemplo anterior, así es como se logra:
char ch = *name;
Esto significa que, obtenga el valor al que apunta la dirección de memoria de 0x9878, ahora ch
tendrá el valor de ''r''. Esto se llama desreferenciación. Simplemente hicimos referencia a un puntero de name
para obtener el valor y asignarlo a ch
.
Además, el compilador sabe que sizeof(char)
es 1, por lo tanto, puede realizar operaciones de incremento / decremento de punteros como este
*name++; *name--;
El puntero automáticamente sube / baja como resultado por uno.
Cuando hacemos esto, asumiendo la dirección de memoria de puntero de 0x9878:
char ch = *name++;
Cuál es el valor de * nombre y cuál es la dirección, la respuesta es, el *name
ahora contendrá ''t'' y lo asignará a ch
, y la dirección de la memoria del puntero es 0x9879.
En este caso, también debe tener cuidado, en el mismo principio y espíritu de lo que se mencionó anteriormente en relación con los límites de la memoria en la primera parte (consulte ''Qué sucede si el nombre [12] se ejecutó'' en la anterior) la los resultados serán los mismos, es decir, el código se bloquea y quema!
Ahora, ¿qué sucede si desasignamos el bloque de memoria al que apunta el name
llamando a la función C free
con el name
como parámetro, es decir, free(name)
:
+======+ +======+ |0x5678| -> |0x0000| -> NULL +======+ +======+
Sí, el bloque de memoria se libera y se devuelve al entorno de ejecución para que lo utilice otra próxima ejecución de código de malloc
.
Ahora, aquí es donde entra en juego la notación común de la falla de segmentación , ya que el name
no apunta a nada, lo que sucede cuando no lo hacemos, es decir
char ch = *name;
Sí, el código se bloqueará y se quemará con un ''Fallo de segmentación'', esto es común en Unix / Linux. En Windows, aparecerá un cuadro de diálogo en la línea de ''Error irrecuperable'' o ''Se produjo un error con la aplicación. ¿Desea enviar el informe a Microsoft?'' ... si el puntero no ha sido malloc
d y Cualquier intento de anularlo, está garantizado para bloquearse y quemarse.
Además, recuerde esto, para cada malloc
hay un free
correspondiente, si no hay un free
correspondiente, tiene una pérdida de memoria en la que la memoria está asignada pero no liberada.
Y ahí lo tienen, así es como funcionan los punteros y cómo los arreglos son diferentes a los punteros, si está leyendo un libro de texto que dice que son lo mismo, quite esa página y destrúyala. :)
Espero que esto sea de ayuda para comprender los indicadores.
En C, una cadena es en realidad solo una matriz de caracteres, como puede ver por la definición. Sin embargo, superficialmente, cualquier arreglo es solo un indicador de su primer elemento, vea a continuación las complejidades sutiles. No hay comprobación de rango en C, el rango que proporciona en la declaración de variable solo tiene significado para la asignación de memoria para la variable.
a[x]
es lo mismo que *(a + x)
, es decir, la desreferencia del puntero se incrementa en x.
si usaste lo siguiente:
char foo[] = "foobar";
char bar = *foo;
la barra se ajustará a ''f''
Para evitar la confusión y evitar personas engañosas, algunas palabras adicionales sobre la diferencia más compleja entre punteros y matrices, gracias avakar:
En algunos casos, un puntero es en realidad semánticamente diferente de una matriz, una lista (no exhaustiva) de ejemplos:
//sizeof
sizeof(char*) != sizeof(char[10])
//lvalues
char foo[] = "foobar";
char bar[] = "baz";
char* p;
foo = bar; // compile error, array is not an lvalue
p = bar; //just fine p now points to the array contents of bar
// multidimensional arrays
int baz[2][2];
int* q = baz; //compile error, multidimensional arrays can not decay into pointer
int* r = baz[0]; //just fine, r now points to the first element of the first "row" of baz
int x = baz[1][1];
int y = r[1][1]; //compile error, don''t know dimensions of array, so subscripting is not possible
int z = r[1]: //just fine, z now holds the second element of the first "row" of baz
Y, finalmente, un poco de curiosidad trivial; ya que a[x]
es equivalente a *(a + x)
, en realidad puede usar, por ejemplo, ''3 [a]'' para acceder al cuarto elemento de la matriz a. Es decir, el siguiente código es perfectamente legal e imprimirá ''b'' el cuarto carácter de la cadena foo.
#include <stdio.h>
int main(int argc, char** argv) {
char foo[] = "foobar";
printf("%c/n", 3[foo]);
return 0;
}
Es realmente confuso. Lo importante a entender y distinguir es que el char name[]
declara array y el char* name
declara puntero. Los dos son animales diferentes.
Sin embargo, la matriz en C puede convertirse implícitamente en puntero a su primer elemento. Esto le da la capacidad de realizar aritmética de punteros e iterar a través de elementos de matriz (no importa elementos de qué tipo, tipo o no). Como @which menciona, puede usar ambos, operador de indexación o aritmética de punteros para acceder a los elementos de la matriz. De hecho, el operador de indexación es solo un azúcar sintáctico (otra representación de la misma expresión) para la aritmética de punteros.
Es importante distinguir la diferencia entre matriz y puntero al primer elemento de la matriz. Es posible consultar el tamaño de la matriz declarada como char name[15]
usando el operador sizeof
:
char name[15] = { 0 };
size_t s = sizeof(name);
assert(s == 15);
pero si aplica sizeof
a char* name
, obtendrá el tamaño del puntero en su plataforma (es decir, 4 bytes):
char* name = 0;
size_t s = sizeof(name);
assert(s == 4); // assuming pointer is 4-bytes long on your compiler/machine
Además, las dos formas de definiciones de arrays de elementos char son equivalentes:
char letters1[5] = { ''a'', ''b'', ''c'', ''d'', ''/0'' };
char letters2[5] = "abcd"; /* 5th element implicitly gets value of 0 */
La naturaleza dual de las matrices, la conversión implícita de matriz a puntero a su primer elemento, en lenguaje C (y también C ++), el puntero se puede usar como iterador para recorrer los elementos de matriz:
/ *skip to ''d'' letter */
char* it = letters1;
for (int i = 0; i < 3; i++)
it++;
Eso es un puntero. Lo que significa que es una variable que guarda una dirección en la memoria. Se "apunta" a otra variable.
En realidad, no puede, por sí mismo, contener grandes cantidades de caracteres. Por sí solo, puede contener una sola dirección en la memoria. Si le asigna caracteres en el momento de la creación, asignará espacio para esos caracteres y luego señalará esa dirección. Puedes hacerlo así:
char* name = "Mr. Anderson";
Eso es en realidad más o menos lo mismo que esto:
char name[] = "Mr. Anderson";
El lugar donde los punteros de caracteres son útiles es la memoria dinámica. Puede asignar una cadena de cualquier longitud a un puntero char en cualquier momento en el programa haciendo algo como esto:
char *name;
name = malloc(256*sizeof(char));
strcpy(name, "This is less than 256 characters, so this is fine.");
Alternativamente, puedes asignarlo usando la función strdup()
, como esto:
char *name;
name = strdup("This can be as long or short as I want. The function will allocate enough space for the string and assign return a pointer to it. Which then gets assigned to name");
Si usa un puntero de caracteres de esta manera, y le asigna memoria, debe liberar la memoria contenida en el nombre antes de reasignarla. Me gusta esto:
if(name)
free(name);
name = 0;
Asegúrese de verificar que el nombre sea, de hecho, un punto válido antes de intentar liberar su memoria. Eso es lo que hace la sentencia if.
La razón por la que ve que los punteros de caracteres se usan mucho en C es porque le permiten reasignar la cadena con una cadena de un tamaño diferente. Las matrices de caracteres estáticos no hacen eso. También son más fáciles de pasar.
Además, los punteros de caracteres son útiles porque se pueden utilizar para apuntar a diferentes matrices de caracteres asignadas estáticamente. Me gusta esto:
char *name;
char joe[] = "joe";
char bob[] = "bob";
name = joe;
printf("%s", name);
name = bob;
printf("%s", name);
Esto es lo que sucede a menudo cuando pasa una matriz asignada estáticamente a una función que toma un puntero de carácter. Por ejemplo:
void strcpy(char *str1, char *str2);
Si luego pasas eso
char buffer[256];
strcpy(buffer, "This is a string, less than 256 characters.");
Manipulará ambos a través de str1 y str2, que son solo punteros que apuntan a donde el búfer y la cadena literal se almacenan en la memoria.
Algo a tener en cuenta al trabajar en una función. Si tiene una función que devuelve un puntero de carácter, no devuelva un puntero a una matriz de caracteres estática asignada en la función. Saldrá de alcance y tendrás problemas. Repite, no hagas esto:
char *myFunc() {
char myBuf[64];
strcpy(myBuf, "hi");
return myBuf;
}
Eso no funcionará. Debe usar un puntero y asignar memoria (como se mostró anteriormente) en ese caso. La memoria asignada persistirá entonces, incluso cuando salga del ámbito de funciones. Simplemente no te olvides de liberarlo como se mencionó anteriormente.
Esto terminó un poco más enciclopédico de lo que esperaba, espero que sea útil.
Editado para eliminar el código C ++. Mezclo los dos tan seguido, a veces me olvido.
Uno es un objeto de matriz real y el otro es una referencia o puntero a dicho objeto de matriz.
Lo que puede ser confuso es que ambos tienen la dirección del primer carácter en ellos, pero solo porque una dirección es el primer carácter y la otra dirección es una palabra en la memoria que contiene la dirección del carácter.
La diferencia se puede ver en el valor de &name
. En los dos primeros casos es el mismo valor que solo name
, pero en el tercer caso es un tipo diferente llamado puntero a puntero a char , o **char
, y es la dirección del puntero en sí. Es decir, es un puntero doble-indirecto.
#include <stdio.h>
char name1[] = "fortran";
char *name2 = "fortran";
int main(void) {
printf("%lx/n%lx %s/n", (long)name1, (long)&name1, name1);
printf("%lx/n%lx %s/n", (long)name2, (long)&name2, name2);
return 0;
}
Ross-Harveys-MacBook-Pro:so ross$ ./a.out
100001068
100001068 fortran
100000f58
100001070 fortran
char * nombre es solo un puntero En algún lugar a lo largo de la línea, la memoria debe asignarse y la dirección de esa memoria debe almacenarse en nombre .
- Podría apuntar a un solo byte de memoria y ser un puntero "verdadero" para un solo carácter.
- Podría apuntar a un área contigua de la memoria que contiene una cantidad de caracteres.
- Si esos caracteres terminan con un terminador nulo, baja y he aquí que tiene un puntero a una cadena.
char *name
, por sí solo, no puede contener ningún personaje . Esto es importante.
char *name
simplemente declara que el name
es un puntero (es decir, una variable cuyo valor es una dirección) que se utilizará para almacenar la dirección de uno o más caracteres en algún punto más adelante en el programa. Sin embargo, no asigna ningún espacio en la memoria para contener realmente esos caracteres, ni garantiza que el name
contenga una dirección válida. De la misma manera, si tiene una declaración como int number
no hay forma de saber cuál es el valor del number
hasta que lo establezca explícitamente.
Al igual que después de declarar el valor de un entero, más tarde puede establecer su valor ( number = 42
), después de declarar un puntero a char, más tarde puede establecer que su valor sea una dirección de memoria válida que contenga un carácter, o una secuencia de personajes - que te interesan