Devolver una cadena C de una función

109

Estoy tratando de devolver una cadena C de una función, pero no funciona. Aquí está mi código.

char myFunction()
{
    return "My String";
}

En mainlo llamo así:

int main()
{
  printf("%s", myFunction());
}

También probé otras formas myFunction, pero no funcionan. Por ejemplo:

char myFunction()
{
  char array[] = "my string";
  return array;
}

Nota: ¡No puedo usar punteros!

Poco antecedentes sobre este problema:

Hay una función que es averiguar qué mes es. Por ejemplo, si es 1, devuelve enero, etc.

Así que cuando se va a imprimir, lo está haciendo de esta manera: printf("Month: %s",calculateMonth(month));. Ahora el problema es cómo devolver esa cadena de la calculateMonthfunción.

itsaboutcode
fuente
10
Desafortunadamente, necesita punteros en este caso.
Nick Bedford
1
@Hayato Bueno, creo que somos adultos aquí y sé que debería devolver 0, fue solo por dar un ejemplo de lox ..
itsaboutcode
3
return 0está implícito de forma predeterminada solo en C99 (y C ++) pero no en C90.
hrnt
1
Entonces no podrás hacerlo, además de trucos tontos que de todos modos son manipulaciones de punteros. Los punteros existen por una razón ...: |
GManNickG

Respuestas:

222

La firma de su función debe ser:

const char * myFunction()
{
    return "My String";
}

Antecedentes:

Es tan fundamental para C & C ++, pero un poco más de discusión debería estar en orden.

En C (y C ++ para el caso), una cadena es solo una matriz de bytes terminados con un byte cero; de ahí que el término "cadena-cero" se use para representar este tipo particular de cadena. Hay otros tipos de cadenas, pero en C (& C ++), este sabor es inherentemente entendido por el lenguaje mismo. Otros lenguajes (Java, Pascal, etc.) utilizan diferentes metodologías para entender "mi cadena".

Si alguna vez usa la API de Windows (que está en C ++), verá parámetros de función con bastante regularidad como: "LPCSTR lpszName". La parte 'sz' representa esta noción de 'cadena-cero': una matriz de bytes con un terminador nulo (/ cero).

Aclaración:

Por el bien de esta 'introducción', utilizo la palabra 'bytes' y 'caracteres' indistintamente, porque es más fácil de aprender de esta manera. Tenga en cuenta que existen otros métodos (caracteres anchos y sistemas de caracteres multibyte ( mbcs )) que se utilizan para hacer frente a caracteres internacionales. UTF-8 es un ejemplo de mbcs. Por el bien de la introducción, silenciosamente 'salto' todo esto.

Memoria:

Esto significa que una cadena como "mi cadena" en realidad usa 9 + 1 (= 10!) Bytes. Es importante saber esto cuando finalmente pueda asignar cadenas de forma dinámica.

Entonces, sin este 'cero final', no tiene una cadena. Tiene una matriz de caracteres (también llamada búfer) en la memoria.

Longevidad de los datos:

El uso de la función de esta manera:

const char * myFunction()
{
    return "My String";
}

int main()
{
    const char* szSomeString = myFunction(); // Fraught with problems
    printf("%s", szSomeString);
}

... generalmente lo llevará a fallas de segmento / excepciones no manejadas aleatorias y similares, especialmente 'en el camino'.

En resumen, aunque mi respuesta es correcta: 9 de cada 10 veces terminará con un programa que falla si lo usa de esa manera, especialmente si cree que es una 'buena práctica' hacerlo de esa manera. En resumen: generalmente no lo es.

Por ejemplo, imagine que en algún momento en el futuro, la cadena ahora debe manipularse de alguna manera. Generalmente, un codificador 'tomará el camino fácil' e (intentará) escribir código como este:

const char * myFunction(const char* name)
{
    char szBuffer[255];
    snprintf(szBuffer, sizeof(szBuffer), "Hi %s", name);
    return szBuffer;
}

Es decir, su programa fallará porque el compilador (puede / puede que no) haya liberado la memoria utilizada szBufferen el momento printf()en que main()se invoca. (Su compilador también debería advertirle de estos problemas de antemano).

Hay dos formas de devolver cadenas que no vomitarán tan fácilmente.

  1. devolviendo búferes (estáticos o asignados dinámicamente) que viven por un tiempo. En C ++ use 'clases auxiliares' (por ejemplo, std::string) para manejar la longevidad de los datos (lo que requiere cambiar el valor de retorno de la función), o
  2. pasar un búfer a la función que se completa con información.

Tenga en cuenta que es imposible utilizar cadenas sin utilizar punteros en C. Como he mostrado, son sinónimos. Incluso en C ++ con clases de plantilla, siempre se utilizan búferes (es decir, punteros) en segundo plano.

Entonces, para responder mejor a la (pregunta ahora modificada). (Seguramente habrá una variedad de 'otras respuestas' que se pueden proporcionar).

Respuestas más seguras:

Ejemplo 1, usando cadenas asignadas estáticamente:

const char* calculateMonth(int month)
{
    static char* months[] = {"Jan", "Feb", "Mar" .... };
    static char badFood[] = "Unknown";
    if (month<1 || month>12)
        return badFood; // Choose whatever is appropriate for bad input. Crashing is never appropriate however.
    else
        return months[month-1];
}

int main()
{
    printf("%s", calculateMonth(2)); // Prints "Feb"
}

Lo que hace aquí lo 'estático' (a muchos programadores no les gusta este tipo de 'asignación') es que las cadenas se colocan en el segmento de datos del programa. Es decir, está asignado de forma permanente.

Si pasa a C ++, usará estrategias similares:

class Foo
{
    char _someData[12];
public:
    const char* someFunction() const
    { // The final 'const' is to let the compiler know that nothing is changed in the class when this function is called.
        return _someData;
    }
}

... pero probablemente sea más fácil usar clases auxiliares, por ejemplo std::string, si está escribiendo el código para su propio uso (y no como parte de una biblioteca para compartir con otros).

Ejemplo 2, usando búferes definidos por el llamador:

Esta es la forma más 'infalible' de pasar cadenas. Los datos devueltos no están sujetos a manipulación por parte de la parte que llama. Es decir, una parte que llama puede abusar fácilmente del ejemplo 1 y exponerlo a fallas en la aplicación. De esta manera, es mucho más seguro (aunque usa más líneas de código):

void calculateMonth(int month, char* pszMonth, int buffersize)
{
    const char* months[] = {"Jan", "Feb", "Mar" .... }; // Allocated dynamically during the function call. (Can be inefficient with a bad compiler)
    if (!pszMonth || buffersize<1)
        return; // Bad input. Let junk deal with junk data.
    if (month<1 || month>12)
    {
        *pszMonth = '\0'; // Return an 'empty' string
        // OR: strncpy(pszMonth, "Bad Month", buffersize-1);
    }
    else
    {
        strncpy(pszMonth, months[month-1], buffersize-1);
    }
    pszMonth[buffersize-1] = '\0'; // Ensure a valid terminating zero! Many people forget this!
}

int main()
{
    char month[16]; // 16 bytes allocated here on the stack.
    calculateMonth(3, month, sizeof(month));
    printf("%s", month); // Prints "Mar"
}

Hay muchas razones por las que el segundo método es mejor, especialmente si está escribiendo una biblioteca para que la utilicen otros (no es necesario que se bloquee en un esquema de asignación / desasignación particular, los terceros no pueden descifrar su código, y no es necesario que se vincule a una biblioteca de administración de memoria específica), pero como todo código, usted decide lo que más le guste. Por esa razón, la mayoría de las personas optan por el ejemplo 1 hasta que se han quemado tantas veces que se niegan a escribirlo de esa manera;)

Descargo de responsabilidad:

Me jubilé hace varios años y mi C está un poco oxidada ahora. Este código de demostración debería compilarse correctamente con C (aunque está bien para cualquier compilador de C ++).

cmroanirgo
fuente
2
En realidad, la función debe devolver a char *, ya que los literales de cadena en C son de tipo char[]. Sin embargo, no deben modificarse de ninguna manera, por lo que const char*se prefiere regresar (consulte securecoding.cert.org/confluence/x/mwAV ). La devolución char *puede ser necesaria si la cadena se usará en una función de biblioteca externa o heredada que (desafortunadamente) espera un char*argumento como, incluso aunque solo se leerá de ella. C ++, por otro lado, tiene literales de cadena de const char[]tipo (y, desde C ++ 11, también puede tener std::stringliterales).
TManhente
17
@cmroanirgo el prefijo my declara al lector que la función fue creada por el usuario. Lo encuentro perfectamente razonable de usar en tal contexto.
cuant
4
de acuerdo con aquí: stackoverflow.com/questions/9970295/… , puede devolver literal de cadena
giorgim
6
El código marcado fraught with problemsen la sección "Longevidad de los datos" es en realidad perfectamente válido. Los literales de cadena tienen vidas útiles estáticas en C / C ++. Vea el enlace que menciona Giorgi arriba.
Chengiz
1
@cmroanirgo Devolver cadenas literales es una buena práctica y un buen estilo. No está "plagado de problemas", y no se bloqueará 9 de cada 10 veces: nunca se bloqueará. Incluso los compiladores de los 80 (al menos los que he usado) admiten correctamente la vida útil ilimitada de los literales de cadena. Nota: No estoy seguro de lo que quiso decir con editar la respuesta: todavía veo que dice que es propenso a fallar.
cesss
12

La cadena de CA se define como un puntero a una matriz de caracteres.

Si no puede tener punteros, por definición no puede tener cadenas.

Crashworks
fuente
Puede pasar de una matriz a una función y luego operar sobre esa matriz: void foo( char array[], int length). Por supuesto, arrayes un puntero bajo el capó, pero no es un puntero "explícitamente" y, por lo tanto, puede ser más intuitivo para alguien que está aprendiendo matrices pero que no ha aprendido los punteros.
jvriesem
12

Tenga en cuenta esta nueva función:

const char* myFunction()
{
    static char array[] = "my string";
    return array;
}

Definí "matriz" como estática. De lo contrario, cuando finaliza la función, la variable (y el puntero que está devolviendo) queda fuera de alcance. Dado que la memoria se asigna en la pila, es y será corromperse. La desventaja de esta implementación es que el código no es reentrante ni seguro para subprocesos.

Otra alternativa sería usar malloc para asignar la cadena en el montón y luego liberar en las ubicaciones correctas de su código. Este código será reentrante y seguro para subprocesos.

Como se señaló en el comentario, esta es una muy mala práctica, ya que un atacante puede luego inyectar código a su aplicación (él / ella necesita abrir el código usando GDB, luego hacer un punto de interrupción y modificar el valor de una variable devuelta para desbordar y la diversión acaba de comenzar).

Es mucho más recomendable dejar que la persona que llama se encargue de las asignaciones de memoria. Vea este nuevo ejemplo:

char* myFunction(char* output_str, size_t max_len)
{
   const char *str = "my string";
   size_t l = strlen(str);
   if (l+1 > max_len) {
      return NULL;
   }
   strcpy(str, str, l);
   return input;
}

Tenga en cuenta que el único contenido que se puede modificar es el que tiene el usuario. Otro efecto secundario: este código ahora es seguro para subprocesos, al menos desde el punto de vista de la biblioteca. El programador que llama a este método debe verificar que la sección de memoria utilizada sea segura para subprocesos.

elcuco
fuente
2
Generalmente, esta es una mala forma de hacer las cosas. El char * puede ser manipulado por el código circundante. Es decir, puede hacer cosas como esta: strcpy (myFunction (), "Una cadena muy larga"); y su programa se bloqueará debido a una infracción de acceso.
cmroanirgo
Falta algo cerca de "el que el usuario" .
Peter Mortensen
8

Su problema es con el tipo de retorno de la función; debe ser:

char *myFunction()

... y luego su formulación original funcionará.

Tenga en cuenta que no puede tener cadenas C sin que los punteros estén involucrados, en algún lugar a lo largo de la línea.

Además: suba las advertencias del compilador. Debería haberte advertido sobre esa línea de retorno que convierte char *a charsin una conversión explícita.

coste y flete
fuente
1
Creo que la firma debe const char * ya que la cadena es literal, pero si no me equivoco, el compilador aceptará esto.
Luke
5

Según su historia de fondo recién agregada con la pregunta, ¿por qué no devolver un número entero del 1 al 12 para el mes y dejar que la función main () use una instrucción de cambio o una escalera if-else para decidir qué imprimir? Ciertamente no es la mejor manera de hacerlo, sería char *, pero en el contexto de una clase como esta, imagino que probablemente sea la más elegante.

Twisol
fuente
3

Puede crear la matriz en el llamador, que es la función principal, y pasar la matriz al destinatario de la llamada, que es su myFunction (). Por lo tanto, myFunction puede llenar la cadena en la matriz. Sin embargo, debe declarar myFunction () como

char* myFunction(char * buf, int buf_len){
  strncpy(buf, "my string", buf_len);
  return buf;
}

Y en la función principal, myFunction debe llamarse de esta manera:

char array[51];
memset(array, 0, 51); /* All bytes are set to '\0' */
printf("%s", myFunction(array, 50)); /* The buf_len argument  is 50, not 51. This is to make sure the string in buf is always null-terminated (array[50] is always '\0') */

Sin embargo, todavía se usa un puntero.

ChainLooper
fuente
2

El tipo de retorno de su función es un solo carácter ( char). Debe devolver un puntero al primer elemento de la matriz de caracteres. Si no puedes usar punteros, estás jodido. :(

hrnt
fuente
2

O que tal este:

void print_month(int month)
{
    switch (month)
    {
        case 0:
            printf("January");
            break;
        case 1:
            printf("february");
            break;
        ...etc...
    }
}

Y llámelo con el mes que calcula en otro lugar.

Sebastiaan M
fuente
1
+1 no es lo que OP pidió, pero esto es probablemente lo que la tarea espera que hagas, ya que no puede usar punteros.
Vitim.us
Incluso printf usa punteros. Un puntero es como un cuchillo, esencial para vivir y trabajar, pero tienes que sujetarlo por el mango y usar el lado afilado para cortar o lo pasarás mal. La desafortunada colocación de espacios en la definición de la función es un error cerebral para muchos programadores nuevos en C. char * func (char * s); char func (char * s); char func * char * s); son todos iguales pero todos se ven diferentes, y para agravar la confusión, * también es el operador de desreferencia para las variables que son punteros.
Chris Reid
1

A chares solo un carácter de un byte. No puede almacenar la cadena de caracteres, ni es un puntero (que aparentemente no puede tener). Por lo tanto, no puede resolver su problema sin usar punteros (que char[]es azúcar sintáctico).

Nick Bedford
fuente
1

Si realmente no puede usar punteros, haga algo como esto:

char get_string_char(int index)
{
    static char array[] = "my string";
    return array[index];
}

int main()
{
    for (int i = 0; i < 9; ++i)
        printf("%c", get_string_char(i));
    printf("\n");
    return 0;
}

El número mágico 9 es espantoso y este no es un ejemplo de buena programación. Pero usted consigue el punto. Tenga en cuenta que los punteros y las matrices son lo mismo (más o menos), por lo que esto es un poco engañoso.

Sebastiaan M
fuente
Por lo general, si necesita implementar tales soluciones a los problemas de las tareas, sus suposiciones preliminares son incorrectas.
hrnt
1

Bueno, en su código está intentando devolver un String(en C que no es más que una matriz de caracteres terminada en nulo), pero el tipo de retorno de su función es el charque le está causando todos los problemas. En su lugar, debe escribirlo de esta manera:

const char* myFunction()
{

    return "My String";

}

Y siempre es bueno calificar su tipo constmientras asigna literales en C a punteros, ya que los literales en C no se pueden modificar.

Cheshar
fuente
0

El prototipo de su función indica que su función devolverá un carácter. Por lo tanto, no puede devolver una cadena en su función.

usuario224579
fuente
0
char* myFunction()
{
    return "My String";
}

En C, los literales de cadena son matrices con la clase de memoria constante estática, por lo que devolver un puntero a esta matriz es seguro. Hay más detalles en la pregunta de Stack Overflow "Vida útil" de un literal de cadena en C

Oleg Karavan
fuente
0

Devolver cadena de función

#include <stdio.h>

const char* greet() {
  return "Hello";
}

int main(void) {
  printf("%s", greet());
}
apasionados
fuente