¿Las cadenas C siempre terminan en nulo, o depende de la plataforma?

13

En este momento estoy trabajando con sistemas integrados y descubriendo formas de implementar cadenas en un microprocesador sin sistema operativo. Hasta ahora, lo que estoy haciendo es utilizar la idea de tener punteros de caracteres terminados en NULL y tratarlos como cadenas donde NULL significa el final. Sé que esto es bastante común, pero puede que siempre cuento con que este es el caso?

La razón por la que pregunto es que estaba pensando en usar un sistema operativo en tiempo real en algún momento, y me gustaría reutilizar tanto como sea posible mi código actual. Entonces, para las diversas opciones que existen, ¿puedo esperar que las cadenas funcionen igual?

Permítanme ser más específico para mi caso. Estoy implementando un sistema que toma y procesa comandos a través de un puerto serie. ¿Puedo mantener mi código de procesamiento de comando igual y luego esperar que los objetos de cadena creados en el RTOS (que contiene los comandos) se terminen NULL? ¿O sería diferente según el sistema operativo?

Actualizar

Después de que me aconsejaron echar un vistazo a esta pregunta , he determinado que no responde exactamente lo que estoy preguntando. La pregunta en sí es preguntar si siempre se debe pasar la longitud de una cadena, que es completamente diferente de lo que estoy preguntando, y aunque algunas de las respuestas tenían información útil, no son exactamente lo que estoy buscando. Las respuestas allí parecían dar razones de por qué o por qué no terminar una cadena con un carácter nulo. La diferencia con lo que estoy preguntando es si puedo esperar más o menos que las cadenas innatas de diferentes plataformas terminen sus propias cadenas con nulo, sin tener que salir y probar cada plataforma por ahí si eso tiene sentido.

Fisgonear
fuente
3
No he usado C en mucho tiempo, pero no puedo pensar en un momento en que me encontré con una implementación que no usaba cadenas terminadas en NULL. Es parte del estándar C, si no recuerdo mal (como dije, ha pasado un tiempo ...)
MetalMikester
1
No soy especialista en C, pero que yo sepa, todas las cadenas en C son matrices de caracteres, terminados en nulo. Sin embargo, puede crear su propio tipo de cadena, pero tendría que implementar todas las funciones de manipulación de cadena usted mismo.
Machado
1
@MetalMikester ¿Cree que esta información se puede encontrar en la especificación C estándar?
Snoop
3
@Snoopy Lo más probable es que sí. Pero realmente, cuando se habla de cadenas en C, son solo una serie de caracteres que terminan en NULL y eso es todo, a menos que use algún tipo de biblioteca de cadenas no estándar, pero eso no es de lo que estamos hablando aquí de todos modos. Dudo que encuentre una plataforma que no respete eso, especialmente con una de las fortalezas de C: la portabilidad.
MetalMikester

Respuestas:

42

Las cosas que se denominan "cadenas C" se anularán en cualquier plataforma. Así es como las funciones estándar de la biblioteca C determinan el final de una cadena.

Dentro del lenguaje C, no hay nada que le impida tener una serie de caracteres que no termina en un valor nulo. Sin embargo, tendrá que usar algún otro método para evitar correr al final de una cadena.

Simon B
fuente
44
solo para agregar; generalmente tiene un número entero en algún lugar para realizar un seguimiento de la longitud de la cadena y luego termina con una estructura de datos personalizada para hacerlo bien, algo así como la clase QString en Qt
Rudolf Olah
8
Caso en cuestión: trabajo con un programa en C que utiliza al menos cinco formatos de cadena diferentes: charmatrices terminadas en nulo , charmatrices con la longitud codificada en el primer byte (comúnmente conocido como "cadenas Pascal"), wchar_tversiones basadas en ambos arriba, y charmatrices que combinan ambos métodos: longitud codificada en el primer byte y un carácter nulo que termina la cadena.
Mark
44
@ Mark ¿Interfaz con muchos componentes / aplicaciones de terceros o un desorden de código heredado?
Dan Is Fiddling By Firelight
2
@DanNeely, todo lo anterior. Cadenas Pascal para interactuar con MacOS clásico, cadenas C para uso interno y Windows, cadenas anchas para agregar soporte Unicode y cadenas bastardas porque alguien intentó ser inteligente y hacer una cadena que pudiera interactuar con MacOS y Windows al mismo tiempo.
Mark
1
@Mark ... y, por supuesto, nadie está dispuesto a gastar dinero para pagar la deuda técnica porque el MacOS clásico está muerto hace mucho tiempo, y las cuerdas bastardas son un doble clusterfrak cada vez que necesitan ser tocadas. Mis condolencias.
Dan Is Fiddling By Firelight
22

La determinación del carácter final depende del compilador para literales y la implementación de la biblioteca estándar para cadenas en general. No está determinado por el sistema operativo.

La convención de NULterminación se remonta al C estándar anterior, y en más de 30 años, no puedo decir que me he encontrado con un entorno que hace cualquier otra cosa. Este comportamiento fue codificado en C89 y continúa siendo parte del estándar del lenguaje C (el enlace es a un borrador de C99):

  • La sección 6.4.5 establece el escenario para las NULcadenas terminadas al requerir que NULse agregue a los literales de cadena.
  • La Sección 7.1.1 trae eso a las funciones en la biblioteca estándar al definir una cadena como "una secuencia contigua de caracteres terminados e incluyendo el primer carácter nulo".

No hay ninguna razón por la que alguien no pueda escribir funciones que manejen cadenas terminadas por algún otro carácter, pero tampoco hay razón para romper el estándar establecido en la mayoría de los casos a menos que su objetivo sea ajustar los programadores. :-)

Blrfl
fuente
2
Una razón sería evitar tener que encontrar el final de la misma cadena una y otra vez.
Paŭlo Ebermann
@ PaŭloEbermann Derecha. A expensas de tener que pasar dos valores en lugar de uno. Lo cual es un poco molesto si solo pasa un literal de cadena como en printf("string: \"%s\"\n", "my cool string"). La única forma de pasar cuatro parámetros en este caso (aparte de algún tipo de byte de terminación) sería definir una cadena para que sea algo así como std::stringen C ++, que tiene sus propios problemas y limitaciones.
cmaster - reinstalar a monica el
1
La Sección 6.4.5 no requiere que un literal de cadena se termine con un carácter nulo. Explícitamente notas " cadena de caracteres Una necesidad literal no sea una cadena (ver 7.1.1), porque un carácter nulo puede ser embebido en él por una secuencia \ 0 de escape. "
bzeaman
1
@bzeaman La nota al pie de página dice que puede construir un literal de cadena que no cumpla con la definición de cadena de 7.1.1, pero la oración que hace referencia a él dice que los compiladores compatibles NULlos determinan sin importar qué: "En la fase de traducción 7, un byte o código de valor cero se agrega a cada secuencia de caracteres multibyte que resulta de una cadena literal o literales ". Las funciones de la biblioteca que usan la definición de 7.1.1 se detienen en el primer momento NULque encuentran y no sabrán ni les importará que existan caracteres adicionales más allá.
Blrfl
Estoy corregido. Busqué varios términos como "nulo", pero omití 6.4.5.5 mencionar el "valor cero".
bzeaman
3

Estoy trabajando con sistemas integrados ... sin sistema operativo ... estoy ... usando la idea de tener punteros de caracteres terminados en NULL y tratándolos como cadenas donde el NULL significa el final. Sé que esto es bastante común, pero ¿puedes contar con que este sea el caso?

No hay ningún tipo de datos de cadena en el lenguaje C, pero hay literales de cadena .

Si coloca un literal de cadena en su programa, generalmente terminará en NUL (pero vea el caso especial, discutido en los comentarios a continuación). Es decir, si coloca "foobar"en un lugar donde const char *se espera un valor, el compilador emitirá foobar⊘a la sección / segmento de código / constante de su programa, y ​​el valor de la expresión será un puntero a la dirección donde almacenó el fcarácter. (Nota: estoy usando para significar el byte NUL).

El único otro sentido en el que el lenguaje C tiene cadenas es que tiene algunas rutinas de biblioteca estándar que operan en secuencias de caracteres terminadas en NUL. Esas rutinas de biblioteca no existirán en un entorno de metal desnudo a menos que las porte usted mismo.

Son solo código --- no es diferente del código que usted mismo escribe. Si no los rompe cuando los transfiere, harán lo que siempre hacen (por ejemplo, detenerse en un NUL).

Salomón lento
fuente
2
Re: "Si pone un literal de cadena en su programa, siempre terminará NUL": ¿Está seguro de eso? Estoy bastante seguro de que (por ejemplo) char foo[4] = "abcd";es una forma válida de crear una matriz no terminada en nulo de cuatro caracteres.
ruakh
2
@ruakh, ¡Uy! ese es un caso que no consideré. Estaba pensando en un literal de cadena que aparece en un lugar donde se espera una char const * expresión . Olvidé que los inicializadores C a veces pueden obedecer diferentes reglas.
Solomon Slow
@ruakh El literal de cadena tiene terminación NUL. La matriz no lo es.
jamesdlin
2
@ruakh tienes un char[4]. Eso no es una secuencia, pero se inicializó a partir de una
Caleth
2
@Caleth, "inicializado desde uno" no es algo que deba suceder en tiempo de ejecución. Si agregamos la palabra clave statical ejemplo de Ruakh, entonces el compilador puede emitir un "abcd" no terminado en NUL a un segmento de datos inicializado para que el cargador del programa inicialice la variable. Entonces, Ruakh tenía razón: hay al menos un caso en el que la aparición de un literal de cadena en un programa no requiere que el compilador emita una cadena terminada en NUL. (ps, en realidad compilé el ejemplo con gcc 5.4.0, y el compilador no emitió el NUL.)
Solomon Slow
2

Como otros han mencionado, la terminación nula de cadenas es una convención de la Biblioteca estándar de C. Puede manejar cadenas de la forma que desee si no va a utilizar la biblioteca estándar.

Esto es cierto para cualquier sistema operativo con un compilador 'C' y, además, puede escribir programas 'C' que no se ejecuten bajo un verdadero sistema operativo como menciona en su pregunta. Un ejemplo sería el controlador de una impresora de inyección de tinta que diseñé una vez. En sistemas embebidos, la sobrecarga de memoria de un sistema operativo puede no ser necesaria.

En situaciones de poca memoria, miraría las características de mi compilador frente al conjunto de instrucciones del procesador, por ejemplo. En una aplicación donde las cadenas se procesan mucho, puede ser conveniente utilizar descriptores como la longitud de la cadena. Estoy pensando en un caso en el que la CPU es particularmente eficiente al trabajar con desplazamientos cortos y / o desplazamientos relativos con registros de direcciones.

Entonces, ¿qué es más importante en su aplicación: el tamaño y la eficiencia del código, o la compatibilidad con un sistema operativo o biblioteca? Otra consideración podría ser la mantenibilidad. Cuanto más se aleje de la convención, más difícil será para otra persona mantenerla.

Hugh Buntu
fuente
1

Otros han abordado el problema de que en C, las cadenas son en gran medida lo que usted hace de ellas. Pero parece haber cierta confusión en su pregunta sobre el terminador mismo, y desde una perspectiva, esto podría ser lo que preocupa a alguien en su posición.

Las cadenas C tienen terminación nula. Es decir, que se terminan con el carácter nulo, NUL. No están terminados por el puntero nulo NULL, que es un tipo de valor completamente diferente con un propósito completamente diferente.

NULse garantiza que tiene el valor entero cero. Dentro de la cadena, también tendrá el tamaño del tipo de carácter subyacente, que generalmente será 1.

NULLno se garantiza que tenga un tipo entero en absoluto. NULLestá diseñado para su uso en un contexto de puntero, y generalmente se espera que tenga un tipo de puntero, que no debería convertirse en un carácter o un entero si su compilador es bueno. Si bien la definición de NULLinvolucra el glifo 0, no se garantiza que tenga ese valor [1], y a menos que su compilador implemente la constante como un carácter #define(muchos no lo hacen, porque NULL realmente no debería ser significativo en un no contexto del puntero), por lo tanto, no se garantiza que el código expandido realmente implique un valor cero (aunque confusamente implique un glifo cero).

Si NULLse escribe, también será poco probable que tenga un tamaño de 1 (u otro tamaño de caracteres). Esto puede causar problemas adicionales, aunque las constantes de caracteres reales tampoco tienen el tamaño de caracteres en su mayor parte.

Ahora, la mayoría de la gente verá esto y pensará: "puntero nulo como algo más que todos los bits cero? ¿Qué tontería?", Pero suposiciones como esa solo son seguras en plataformas comunes como x86. Como ha mencionado explícitamente su interés en apuntar a otras plataformas, debe tener en cuenta este problema, ya que ha separado explícitamente su código de los supuestos sobre la naturaleza de la relación entre punteros y enteros.

Por lo tanto, aunque las cadenas C están terminadas en nulo, no están terminadas por NULL, sino por NUL(generalmente escritas '\0'). El código que se usa explícitamente NULLcomo un terminador de cadena funcionará en plataformas con una estructura de dirección sencilla e incluso se compilará con muchos compiladores, pero no es absolutamente correcto C.


[1] el compilador inserta el valor de puntero nulo real cuando lee un 0 token en un contexto donde se convertiría a un tipo de puntero. Esto no es una conversión del valor entero 0, y no se garantiza que se mantenga si 0se usa algo más que el token en sí, como un valor dinámico de una variable; la conversión tampoco es reversible, y un puntero nulo no tiene que producir el valor 0 cuando se convierte en un entero.

Leushenko
fuente
Gran punto He enviado una edición para ayudar a aclarar esto.
Monty Harder
" NULse garantiza que tiene el valor entero cero". -> C no define NUL. En cambio, C define que las cadenas tienen un carácter nulo final , un byte con todos los bits establecidos en 0.
chux - Vuelva a instalar Monica
1

He estado usando cadenas en C, significa que los caracteres con terminación nula se llaman cadenas.

No tendrá ningún problema cuando lo use en baremetal o en cualquier sistema operativo como Windows, Linux, RTOS: (FreeRTO, OSE).

En el mundo incrustado, la terminación nula en realidad ayuda a simular más el carácter como una cadena.

He estado usando cadenas en C así en muchos sistemas críticos de seguridad.

Tal vez se pregunte, ¿qué es la cadena realmente en C?

Cadenas de estilo C, que son matrices, también hay literales de cadena, como "this". En realidad, estos dos tipos de cadenas no son más que colecciones de caracteres sentados uno al lado del otro en la memoria.

Cada vez que escribe una cadena, entre comillas dobles, C crea automáticamente una matriz de caracteres para nosotros, que contiene esa cadena, terminada por el carácter \ 0.

Por ejemplo, puede declarar y definir una matriz de caracteres e inicializarlo con una constante de cadena:

char string[] = "Hello cruel world!";

Respuesta directa: realmente no necesita preocuparse por el uso de caracteres con terminación nula, esto funciona independientemente de cualquier plataforma.

puntero
fuente
Gracias, no sabía que cuando se declara con comillas dobles, a NULse agrega automáticamente.
Snoop
1

Como otros han dicho, la terminación nula es bastante universal para el estándar C. Pero (como otros también han señalado) no es 100%. Para (otro) ejemplo, el sistema operativo VMS usualmente usaba lo que llamó "descriptores de cadena" http://h41379.www4.hpe.com/commercial/c/docs/5492p012.html accedido en C por #include <descrip.h >

Las cosas a nivel de aplicación pueden usar terminación nula o no, sin embargo, el desarrollador lo considera conveniente. Pero las cosas de VMS de bajo nivel requieren absolutamente descriptores, que no usan terminación nula (ver el enlace anterior para más detalles). Esto es en gran medida para que todos los lenguajes (C, ensamblaje, etc.) que usan directamente componentes internos de VMS puedan tener una interfaz común con ellos.

Por lo tanto, si está anticipando algún tipo de situación similar, es posible que desee ser algo más cuidadoso de lo que sugeriría la "terminación nula universal". Tendría más cuidado si estuviera haciendo lo que está haciendo, pero para mis cosas a nivel de aplicación es seguro asumir una terminación nula. Simplemente no te recomendaría el mismo nivel de seguridad. Es posible que su código tenga que interactuar con el ensamblado y / u otro código de idioma en algún momento futuro, que no siempre se ajusta al estándar C de las cadenas terminadas en nulo.

John Forkosh
fuente
Hoy, la terminación 0 es bastante inusual. C ++ std :: string no, Java String no, Objective-C NSString no, Swift String no - como resultado, cada biblioteca de idiomas admite cadenas con códigos NUL dentro de la cadena (lo cual es imposible con C cadenas por razones obvias).
gnasher729
@ gnasher729 Cambié "... bastante universal" a "bastante universal para el estándar C", lo que espero elimine cualquier ambigüedad y siga siendo correcto hoy (y eso es lo que quise decir, según el tema y la pregunta del OP).
John Forkosh
0

En mi experiencia con sistemas embebidos, críticos para la seguridad y en tiempo real, no es raro usar las convenciones de cadenas C y PASCAL, es decir, proporcionar la longitud de las cadenas como primer carácter (que limita la longitud a 255) y finalizar el cadena con al menos un 0x00, ( NUL), que reduce el tamaño utilizable a 254.

Una razón para esto es saber cuántos datos espera después de que se haya recibido el primer byte y otra es que, en tales sistemas, se evitan los tamaños dinámicos del búfer cuando sea posible: la asignación de un tamaño de búfer 256 fijo es más rápido y seguro (no necesita verificar si mallocfalló). Otra es que los otros sistemas con los que se está comunicando pueden no estar escritos en ANSI-C.

En cualquier trabajo integrado, es importante establecer y mantener un Documento de Control de Interfaz (IDC) que defina todas sus estructuras de comunicación, incluidos formatos de cadena, endianness, tamaños enteros, etc., lo antes posible ( idealmente antes de comenzar ), y debe ser su libro sagrado, y todos los equipos, al escribir el sistema: si alguien desea introducir una nueva estructura o formato, primero debe documentarse allí y todos los que puedan verse afectados deben estar informados, posiblemente con la opción de vetar el cambio .

Steve Barnes
fuente