C Строки неравной длины, строчные буквы в прописные

Я пытаюсь реализовать функцию, которая получает на вход строку c, преобразует все символы нижнего регистра в верхний регистр, а затем сохраняет результат в выходном параметре. Вот код этой функции:

void makeUpper( const unsigned char* input, unsigned char* output ) 
{

    int inputLength = strlen((char*)input);
    int outputLength = strlen((char*)output);

    for (int i = 0; i < inputLength; i++)
    {
        if ((input[i] >= 97) && (input[i] <= 122))
        {
            output[i] = input[i] - 32;
        }
        else
        {
            output[i] = input[i];
        }
    }

}

Теперь проблема явно возникнет в случае, когда inputLength > outputLength. Чтобы исправить это, я вставил следующий код между объявлениями inputLength и outputLength и циклом for.

if (inputLength > outputLength)
{
    for (int i = 0; i < (inputLength - outputLength); i++)
    {
        strcat((char*)output, " ");
    }
}

Это не только вызывает ошибку (эта функция или переменная могут быть небезопасными...), но я почти уверен, что делаю это неправильно. Однако я не могу думать ни о каких альтернативах.

РЕДАКТИРОВАТЬ:

Основная функция, которую я использую, выглядит следующим образом:

int main() 
{

  unsigned char in[] = "HELLO aaaaaaaaaa 678";
  unsigned char out[] = "                    xxxxxxxxx";

  makeUpper( in, out );
  cout << in << " -> " << out << endl;
  makeUpper( out, in );
  cout << out << " -> " << in << endl;

 return 0;

}

То, что я функция должна напечатать, это:

HELLO aaaaaaaaaa 678 -> HELLO AAAAAAAAAA 678xxxxxxxxx
HELLO AAAAAAAAAA 678xxxxxxxxx -> HELLO AAAAAAAAAA 678XXXXXXXXX

person Will    schedule 29.10.2013    source источник
comment
Предполагается, что буфер передается потребителями функции, или вы можете malloc буфер самостоятельно?   -  person Ian McLaird    schedule 30.10.2013
comment
@IanMcLaird: strncat() столь же смертоносен, как и strcat(), в первую очередь потому, что вам нужно знать, сколько данных уже хранится в целевой строке, чтобы иметь возможность правильно указать длину, но если вы это знаете, вам едва ли нужно использовать функцию *cat() в все.   -  person Jonathan Leffler    schedule 30.10.2013
comment
Вам нужно либо предположить, что в целевой строке достаточно места (что проще), либо вам нужно передать длину целевой строки в функцию в качестве аргумента.   -  person Jonathan Leffler    schedule 30.10.2013
comment
@JonathanLeffler, вы абсолютно правы, и я удалил свой предыдущий комментарий (надеюсь), прежде чем кто-нибудь воспримет его как хороший совет.   -  person Ian McLaird    schedule 30.10.2013


Ответы (2)


Вы путаете «текущее содержимое выходного параметра» с «доступным пространством». Первое не имеет значения, а единственная информация, которая у вас есть о последнем, - это «по крайней мере, столько свободного места».

Теперь это будет зависеть от того, как место для output было назначено в первую очередь. Если вы сделали что-то вроде

char output[100];
strcpy(output, "hello");

У вас останется место для 100 символов, но на самом деле используется только 6 (5+1). Таким образом, вы можете без проблем взять строку "THIS IS a STRING" и обработать ее своей функцией.

Но это небезопасно, потому что вы не знаете, сколько там места. Следующий подход был бы лучше:

char *output;
output = malloc(100);

Теперь измените прототип функции на

void makeUpper( const unsigned char* input, unsigned char** output ) 

и в вашей функции вы делаете

inputLength = strlen(input);
*output = realloc(*output, inputLength + 1);

Это гарантирует, что для вывода будет выделено достаточно места. Или вы можете вернуть значение во входном векторе - вы уже знаете, что там достаточно места...

EDIT В приведенном вами примере достаточно места в output; вопрос просто становится одним из «безопасного копирования» ввода (в верхнем регистре) на вывод. В этом случае ваша функция может выглядеть так:

void makeUpper( const unsigned char* input, unsigned char* output ) 
{

    int inputLength = strlen(input);
    int outputLength = strlen(output);
    int ii;
    for (ii = 0; ii < inputLength; ii++)
    {
        output[ii] = toupper(input[ii]);
    }
    if(outputLength < inputLength) output[ii] = '\0';
}

Последняя строка предназначена для того, чтобы убедиться, что если вы увеличили длину output (опять же, предполагая, что это память, к которой вы можете получить действительный доступ), вам все равно нужно убедиться, что в конце строки есть завершающий символ nul. В вашем примере вы хотите, чтобы «остальная часть выходной строки» все еще была там, когда input короче, чем output, поэтому вам нужно условие if.

В общем, если вы не знаете наверняка, что output достаточно велико, нет способа сделать его больше, не имея доступа к адресу указателя, иногда называемого «дескриптором».

person Floris    schedule 29.10.2013
comment
Есть ли способ сделать это без изменения прототипа? - person Will; 30.10.2013
comment
Другой вопрос, когда запускаю программу, все печатает правильно, однако выдает ошибку: Стек вокруг переменной 'in' был поврежден. Это потому, что в in[] хранится больше символов, чем было изначально выделено? - person Will; 31.10.2013
comment
Когда вы делаете второй вызов и копируете out обратно в in, вы действительно превышаете доступное пространство в in и можете в конечном итоге перезаписать части out (хотя то, что будет повреждено, зависит от того, как компилятор выбирает выделение стека - нет никаких фиксированные правила). Так что да - не делайте этого! - person Floris; 31.10.2013

Если можно предположить, что выходной буфер создается внутри функции:

// C++ version
void makeUpper(const unsigned char* input, unsigned char*& output)
{
    // assume output = null
    int inputLength = strlen((const char*)input);
    output = new unsigned char[inputLength + 1];
    memset(output, 0, inputLength + 1); // initialize the array to 0's

    for (int i = 0; i < inputLength; i++)
    {
        output[i] = ::toupper(input[i]); // why reinvent the wheel
    }
}

// C version
void makeUpper(const unsigned char* input, unsigned char** output) 
{
    // assume output = null
    int inputLength = strlen((const char*)input);
    *output = (unsigned char*)malloc((inputLength + 1) * sizeof(unsigned char));
    memset(*output, 0, (inputLength + 1) * sizeof(unsigned char)); // initialize the array to 0's

    for (int i = 0; i < inputLength; i++)
    {
        (*output)[i] = ::toupper(input[i]);
    }
}

output должен быть удален/освобожден тем, кто возьмет его под свой контроль.

Если вы не хотите правильно изменять размер output внутри функции:

void makeUpper(const unsigned char* input, unsigned char* output, unsigned int output_size) 
{
    // assume output != null, the current contents of output are irrelevant - you need it's size
    int inputLength = strlen((const char*)input);
    int maxLength = (inputLength < output_size - 1 ? inputLength : output_size - 1);
    memset(output, 0, output_size); // clear output

    for (int i = 0; i < maxLength; i++)
    {
        output[i] = ::toupper(input[i]); // why reinvent the wheel
    }
}

ПРИМЕЧАНИЕ. Тег C++ был удален, пока я писал свой ответ. Это действительно влияет только на первое решение (поскольку вы использовали бы malloc вместо new). Я бы избегал realloc или calloc, если они не являются абсолютно необходимыми для ваших требований.

person Zac Howland    schedule 29.10.2013
comment
Вы также не можете использовать ссылки в решении C, что немного больше повлияет на ваше первое решение. - person Jonathan Leffler; 30.10.2013
comment
Отсюда и комментарий с двойным указателем под ним. Для ясности я разделил его на версии C++ и C. - person Zac Howland; 30.10.2013