Retornar uma string C de uma função


109

Estou tentando retornar uma string C de uma função, mas não está funcionando. Aqui está meu código.

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

Em mainque estou chamando-o assim:

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

Eu também tentei algumas outras formas para myFunction, mas não estão funcionando. Por exemplo:

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

Nota: Não tenho permissão para usar ponteiros!

Um pequeno histórico sobre este problema:

Existe uma função que é descobrir em que mês estamos. Por exemplo, se for 1, ele retornará janeiro, etc.

Então, quando ele vai imprimir, é fazê-lo como este: printf("Month: %s",calculateMonth(month));. Agora o problema é como retornar essa string da calculateMonthfunção.


10
Infelizmente, você precisa de dicas neste caso.
Nick Bedford

1
@Hayato Bem, eu acredito que somos adultos aqui e sabemos que deveria retornar 0, foi apenas para dar um exemplo lox ..
itsaboutcode

3
return 0está implícito por padrão apenas em C99 (e C ++), mas não em C90.
hrnt

1
Então você não será capaz de fazer isso, além de hacks idiotas que realmente quebram as manipulações do ponteiro. Os ponteiros existem por uma razão ...: |
GManNickG

Respostas:


222

Sua assinatura de função precisa ser:

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

Fundo:

É tão fundamental para C e C ++, mas pouca discussão deveria ser necessária.

Em C (e C ++ para esse assunto), uma string é apenas um array de bytes terminado com um byte zero - portanto, o termo "string-zero" é usado para representar este tipo particular de string. Existem outros tipos de strings, mas em C (e C ++), esse sabor é inerentemente entendido pela própria linguagem. Outras linguagens (Java, Pascal, etc.) usam diferentes metodologias para entender "minha string".

Se você já usou a API do Windows (que está em C ++), verá parâmetros de função com bastante regularidade, como: "LPCSTR lpszName". A parte 'sz' representa esta noção de 'string-zero': uma matriz de bytes com um terminador nulo (/ zero).

Esclarecimento:

Por causa desta 'introdução', uso as palavras 'bytes' e 'caracteres' alternadamente, porque é mais fácil de aprender desta forma. Esteja ciente de que existem outros métodos (caracteres largos e sistemas de caracteres multibyte ( mbcs )) que são usados ​​para lidar com caracteres internacionais. UTF-8 é um exemplo de mbcs. Por causa da introdução, eu silenciosamente 'pulo' tudo isso.

Memória:

Isso significa que uma string como "minha string" na verdade usa 9 + 1 (= 10!) Bytes. É importante saber quando você finalmente conseguirá alocar strings dinamicamente.

Então, sem esse 'zero terminador', você não tem uma string. Você tem uma matriz de caracteres (também chamada de buffer) circulando na memória.

Longevidade dos dados:

O uso da função desta forma:

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

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

... geralmente levará você a exceções aleatórias não tratadas / falhas de segmento e semelhantes, especialmente 'no futuro'.

Resumindo, embora minha resposta esteja correta - 9 em cada 10 vezes, você terminará com um programa que trava se o usar dessa forma, especialmente se você achar que é uma 'boa prática' fazê-lo dessa forma. Resumindo: geralmente não é.

Por exemplo, imagine em algum momento no futuro, a string agora precisa ser manipulada de alguma forma. Geralmente, um codificador irá 'seguir o caminho mais fácil' e (tentar) escrever código como este:

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

Ou seja, seu programa travará porque o compilador (pode / não) liberou a memória usada szBufferno momento printf()em que o in main()é chamado. (Seu compilador também deve avisá-lo sobre esses problemas com antecedência.)

Existem duas maneiras de retornar strings que não vomitam tão prontamente.

  1. retornando buffers (estáticos ou alocados dinamicamente) que vivem por um tempo. Em C ++, use 'classes auxiliares' (por exemplo, std::string) para lidar com a longevidade dos dados (o que requer a alteração do valor de retorno da função), ou
  2. passe um buffer para a função que é preenchida com informações.

Observe que é impossível usar strings sem usar ponteiros em C. Como mostrei, eles são sinônimos. Mesmo em C ++ com classes de modelo, sempre há buffers (ou seja, ponteiros) sendo usados ​​em segundo plano.

Então, para melhor responder a (questão agora modificada). (Com certeza há uma variedade de "outras respostas" que podem ser fornecidas.)

Respostas mais seguras:

Exemplo 1, usando strings alocadas estaticamente:

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"
}

O que o 'estático' faz aqui (muitos programadores não gostam desse tipo de 'alocação') é que as strings são colocadas no segmento de dados do programa. Ou seja, está alocado permanentemente.

Se você mudar para C ++, usará estratégias semelhantes:

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;
    }
}

... mas provavelmente é mais fácil usar classes auxiliares, como, por exemplo std::string, se você estiver escrevendo o código para seu próprio uso (e não parte de uma biblioteca para ser compartilhada com outras pessoas).

Exemplo 2, usando buffers definidos pelo chamador:

Esta é a maneira mais 'infalível' de passar as cordas. Os dados retornados não estão sujeitos à manipulação pela parte chamadora. Ou seja, o exemplo 1 pode facilmente ser abusado por uma parte chamadora e expô-lo a falhas de aplicativo. Dessa forma, é muito mais seguro (embora use mais linhas 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"
}

Existem muitas razões pelas quais o segundo método é melhor, especialmente se você estiver escrevendo uma biblioteca para ser usada por outros (você não precisa se prender a um esquema específico de alocação / desalocação, terceiros não podem quebrar seu código, e você não precisa se vincular a uma biblioteca de gerenciamento de memória específica), mas como todo código, você decide o que mais gosta. Por esse motivo, a maioria das pessoas opta pelo exemplo 1 até que tenham sido queimados tantas vezes que se recusam a escrever mais dessa forma;)

Aviso Legal:

Eu me aposentei há vários anos e meu C está um pouco enferrujado agora. Este código de demonstração deve ser compilado corretamente com C (embora seja OK para qualquer compilador C ++).


2
Na verdade, a função precisa retornar a char *, pois os literais de string em C são do tipo char[]. Eles, entretanto, não devem ser modificados de forma alguma, portanto, o retorno const char*é preferível (consulte securecoding.cert.org/confluence/x/mwAV ). O retorno char *pode ser necessário se a string for usada em uma função de biblioteca legada ou externa que (infelizmente) espera um char*como argumento, mesmo que seja apenas lido dele. C ++, por outro lado, tem literais de string do const char[]tipo (e, desde C ++ 11, você também pode ter std::stringliterais).
TManhente

17
@cmroanirgo o prefixo my declara ao leitor que a função foi criada pelo usuário. Acho perfeitamente razoável usar em tal contexto.
quant

4
de acordo com aqui: stackoverflow.com/questions/9970295/… , você pode retornar string literal
giorgim

6
O código marcado fraught with problemsna seção "Longevidade dos dados" é perfeitamente válido. Literais de string têm tempos de vida estáticos em C / C ++. Veja o link que Giorgi menciona acima.
chengiz

1
@cmroanirgo Retornar literais de string é uma boa prática e um bom estilo. Não é "cheio de problemas" e não travará 9 em cada 10 vezes: Nunca travará. Mesmo os compiladores dos anos 80 (pelo menos os que usei) suportam corretamente o tempo de vida ilimitado de literais de string. Observação: não tenho certeza do que você quis dizer sobre editar a resposta: ainda vejo que diz que está sujeito a travamentos.
cessa

12

A string AC é definida como um ponteiro para uma matriz de caracteres.

Se você não pode ter ponteiros, por definição não pode ter strings.


Você pode passar uma matriz para uma função e, em seguida, operar nessa matriz: void foo( char array[], int length). Obviamente, arrayé um ponteiro por baixo do capô, mas não é "explicitamente" um ponteiro e, portanto, pode ser mais intuitivo para alguém que está aprendendo matrizes, mas que ainda não aprendeu ponteiros.
jvriesem

12

Observe esta nova função:

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

Eu defini "array" como estático. Do contrário, quando a função termina, a variável (e o ponteiro que você está retornando) sai do escopo. Uma vez que essa memória é alocada na pilha, ela será corrompida. A desvantagem dessa implementação é que o código não é reentrante e não é thread-safe.

Outra alternativa seria usar malloc para alocar a string no heap e, em seguida, liberá-la nos locais corretos de seu código. Este código será reentrante e thread-safe.

Conforme observado no comentário, esta é uma prática muito ruim, já que um invasor pode então injetar código em seu aplicativo (ele precisa abrir o código usando GDB, fazer um ponto de interrupção e modificar o valor de uma variável retornada para estourar e a diversão acaba de começar).

É muito mais recomendado deixar o chamador lidar com as alocações de memória. Veja este novo exemplo:

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;
}

Observe que o único conteúdo que pode ser modificado é aquele do usuário. Outro efeito colateral - este código agora é threadsafe, pelo menos do ponto de vista da biblioteca. O programador que chama este método deve verificar se a seção de memória usada é threadsafe.


2
Geralmente é uma maneira ruim de fazer as coisas. O char * pode ser manipulado pelo código circundante. Ou seja, você pode fazer coisas como isto: strcpy (myFunction (), "Uma string muito longa"); e seu programa irá travar devido à violação de acesso.
cmroanirgo

Falta algo próximo a "aquele que o usuário" .
Peter Mortensen

8

Seu problema é com o tipo de retorno da função - deve ser:

char *myFunction()

... e então sua formulação original funcionará.

Observe que você não pode ter strings C sem ponteiros envolvidos, em algum lugar ao longo da linha.

Além disso: Aumente os avisos do compilador. Ele deveria ter avisado sobre aquela linha de retorno convertendo um char *para charsem uma conversão explícita.


1
Eu acho que a assinatura deve const char * já que a string é literal, mas se não estou enganado, o compilador aceitará isso.
Lucas

5

Com base em sua história de fundo recém-adicionada com a pergunta, por que não retornar um número inteiro de 1 a 12 para o mês e deixar a função main () usar uma instrução switch ou uma escada if-else para decidir o que imprimir? Certamente não é a melhor maneira de ir - char * seria - mas no contexto de uma aula como essa, imagino que seja provavelmente a mais elegante.


3

Você pode criar a matriz no chamador, que é a função principal, e passar a matriz para o receptor que é seu myFunction (). Portanto, myFunction pode preencher a string no array. No entanto, você precisa declarar myFunction () como

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

E na função principal, myFunction deve ser chamada desta forma:

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') */

No entanto, um ponteiro ainda é usado.


2

O tipo de retorno de sua função é um único caractere ( char). Você deve retornar um ponteiro para o primeiro elemento da matriz de caracteres. Se você não pode usar ponteiros, está ferrado. :(


2

Ou que tal este:

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

E chame isso com o mês que você computou em outro lugar.


1
+1 não o que OP pediu, mas isso é provavelmente o que a atribuição espera que você faça, já que ele não pode usar ponteiros.
Vitim.us

Até printf usa ponteiros. Um ponteiro é como uma faca - essencial para viver e trabalhar, mas você tem que segurá-lo pelo cabo e usar o lado afiado para cortar ou você vai se divertir muito. A infeliz colocação de espaços na definição de função é um bug cerebral para muitos programadores C novos. char * func (char * s); função char (char * s); char func * char * s); são todos iguais, mas todos parecem diferentes e, para aumentar a confusão, * é também o operador de anulação de referência para variáveis ​​que são ponteiros.
Chris Reid

1

A charé apenas um único caractere de um byte. Ele não pode armazenar a sequência de caracteres, nem é um ponteiro (que aparentemente você não pode ter). Portanto, você não pode resolver seu problema sem usar ponteiros (que char[]é um açúcar sintático).


1

Se você realmente não pode usar ponteiros, faça algo assim:

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;
}

O número mágico 9 é terrível e este não é um exemplo de boa programação. Mas você entendeu. Observe que ponteiros e matrizes são a mesma coisa (mais ou menos), então isso é um pouco trapaça.


Normalmente, se você precisa implementar tais soluções para problemas de dever de casa, então suas suposições preliminares estão erradas.
hrnt

1

Bem, em seu código, você está tentando retornar a String(em C, que nada mais é do que uma matriz de caracteres terminada em nulo), mas o tipo de retorno de sua função é o charque está causando todos os problemas para você. Em vez disso, você deve escrever desta forma:

const char* myFunction()
{

    return "My String";

}

E é sempre bom qualificar seu tipo com constao atribuir literais em C a ponteiros, visto que literais em C não são modificáveis.


0

Seu protótipo de função afirma que sua função retornará um char. Portanto, você não pode retornar uma string em sua função.


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

Em C, os literais de string são arrays com a classe de memória constante estática, portanto, retornar um ponteiro para esse array é seguro. Mais detalhes estão na questão Stack Overflow "Life-time" de um literal de string em C


0

String de retorno da função

#include <stdio.h>

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

int main(void) {
  printf("%s", greet());
}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.