Заполнение строки осталось в неприятностях C - PullRequest
3 голосов
/ 05 декабря 2010

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

Я придумал следующую функцию:

char * strpadleft(char * string, char pad, size_t bytes) {
 size_t ssize = strlen(string);
 size_t bits = bytes * 8;                            
 char *padded = (char *) malloc(bits + 1); /* Bit size + null terminator */
 memset(padded, pad, bits);                /* Fill contents with zeros, leave last null terminator*/
 padded -= ssize + 1;                      /* Rewind back to offset*/
 strncpy(padded, string, ssize);           /* Replace for example bits 16->32 with representation*/
 return padded;
}

/*Example: strpadleft("0100100001", '0', 4); */

К сожалению, теперь возвращается просто незаполненная строка (например, 0100100001). Моя арифметика указателя неверна, я копирую в неправильное место или я пропустил что-то еще, что не позволяет этой работе?

Ответы [ 3 ]

3 голосов
/ 05 декабря 2010

Вот пример того, что происходит с помощью вашего примера вызова, strpadleft («0100100001», «0», 4);

ssize имеет значение 10

бит установлен на 32

Заполнение указывает на выделенную область памяти в 33 байта.

Вот простое / грубое представление выделенной памяти:

   ..............................................
ma 0000000000000000000000000000000000000000000000
ed xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
md 0000000000000000111111111111111222222222222222
yr 0123456789ABCDEF123456789ABCDEF123456789ABCDEF
              |                               |
              |                               padded allocation end
              padded allocation start

Учитывая, что пример дополнения содержит адрес 0x0B.

memset затем устанавливает все байты выделенной памяти для вашего символа пэда '0'.

   ...........00000000000000000000000000000000...
ma 0000000000000000000000000000000000000000000000
ed xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
md 0000000000000000111111111111111222222222222222
yr 0123456789ABCDEF123456789ABCDEF123456789ABCDEF
              |                               |
              |                               "padded" allocation end
              "padded" allocatoin start

Затем вы вычтите 11 из заполненного указателя, теперь заполненный содержит адрес 0x00.

(Это ошибка в вашей логике, вы хотели увеличивать указатель, а не уменьшать его. Вы также, как и другие отмечали, не хотите изменять padded для этого. Вместо этого используйте переменную temp или обязательно заново выполните добавление после копирования строки.)

   0100100001n00000000000000000000000000000000... (Note: 'n' represents the null character here)
ma 0000000000000000000000000000000000000000000000
ed xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
md 0000000000000000111111111111111222222222222222
yr 0123456789ABCDEF123456789ABCDEF123456789ABCDEF
   |          |                               |
   |          |                               "padded" allocation end
   |          "padded" allocatoin start
   "padded" now points here 

Затем вы возвращаете заполненные адресом точки, на которые, как вы можете видеть, указывает начало исходной строки.

Помимо того, что вы не делаете то, что вам нужно, у вас также есть поврежденная память, записываемая за пределы выделения памяти.

Я бы порекомендовал использовать отладчик по вашему выбору и пытаться перебирать код, просматривая ключевые переменные для решения подобных проблем.

2 голосов
/ 05 декабря 2010

Существует серьезное заблуждение и некоторые другие проблемы:

  • memset() не изменяется padded

То есть переменная в вашей функции не изменяется; memset() просто устанавливает данные, на которые указывает padded.

Предполагаемая операция сброса padded -= ssize + 1, следовательно, вызывает неопределенное поведение, обращаясь к памяти, которую вы не выделяли.

Использование:

strcpy(padded + bits - ssize, string);

вместо двух строк:

padded -= ssize + 1;
strncpy(padded, string, ssize);

Использование strcpy() безопасно, потому что вы знаете все размеры.

Обратите внимание, что malloc() не возвращает инициализированные данные, вы не можете гарантировать, что последний выделенный байт будет нулевым. Для этого вам придется использовать calloc().

Обратите внимание, что операция memset() НЕ обнуляет вашу строку.

Обратите внимание, что использование strncpy(), как это ни парадоксально, также не гарантирует нулевое завершение и действительно не завершает нулевую строку, даже если вы правильно указали начальную позицию. Напротив, использование strcpy() гарантирует нулевое завершение.

Рабочий код

Обратите внимание на пересмотренный интерфейс - используйте const char * для первого аргумента. (static просто получает код для компиляции под моими флагами компиляции по умолчанию без жалобы на отсутствие предварительного объявления функции. Конечно, вы не будете использовать это для библиотечной функции, объявленной в заголовке.)

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static char *strpadleft(const char * string, char pad, size_t bytes)
{
    size_t ssize = strlen(string);
    size_t bits = bytes * 8;
    char *padded = (char *) malloc(bits + 1);
    assert(ssize < bits);
    memset(padded, pad, bits - ssize);
    strcpy(padded + bits - ssize, string);
    return padded;
}

int main(void)
{
    const char *data = "0100100001";
    char *pad = strpadleft(data, '0', 4);
    printf("Data: <<%s>> padded <<%s>> (%d)\n", data, pad, (int)strlen(pad));
    free(pad);
    return(0);
}

Комментарий

Вам действительно нужно решить, какое поведение будет подходящим, если ssize > bits (подсказка: assert() неверна). Скорее всего, вы просто дублируете исходную строку. Примечание: абсолютно НЕ будет приемлемо для возврата указателя на исходную строку. Функция возвращает указатель на строку, которая должна быть освобождена приложением; поэтому вы всегда должны возвращать выделенную строку. В противном случае ваша функция станет непригодной для использования; код должен проверить, совпадает ли возвращаемое значение с аргументом, и не освобождать возвращаемое значение, если оно совпадает. Тьфу!

Квазификсированный код

Демонстрация отсутствия нулевого завершения в исходном коде:

static char * strpadleft(const char * string, char pad, size_t bytes)
{
    size_t ssize = strlen(string);
    size_t bits = bytes * 8;
    char *padded = (char *) malloc(bits + 1);
    padded[bits] = 'X';  // Overwrite last allocated byte
    memset(padded, pad, bits);
    strncpy(padded + bits - ssize, string, ssize);
    return padded;
}

С той же тестовой программой, что и раньше, и полагаясь на неопределенное поведение (не было никакой гарантии, что байт после X будет нулевым), я получил:

Data: <<0100100001>> padded <<00000000000000000000000100100001X>> (33)

Обратите внимание, что 'X' не был перезаписан strncpy()! Вы можете исправить это с помощью ssize + 1, но почему бы просто не использовать strcpy() ... как уже было сказано ...

1 голос
/ 05 декабря 2010

Измените строку:

padded -= ssize + 1;
strncpy(padded, string, ssize);           /* Replace for example bits 16->32 with representation*/

на

char *data = padded + (bits - ssize);
strncpy(data , string, ssize);           /* Replace for example bits 16->32 with representation*/
padded [bits] = '\0';

Не меняйте padded, потому что вы должны вернуть это значение, причину, потому что данные переменной созданы, иphihag сказал, что memset не меняет адрес дополнения.

...