RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 990123
Accepted
eanmos
eanmos
Asked:2020-06-07 04:08:15 +0000 UTC2020-06-07 04:08:15 +0000 UTC 2020-06-07 04:08:15 +0000 UTC

短信隐写程序

  • 772

一个简单的程序允许您在文本文件中隐藏文本消息,如下所示:

  1. 短信被翻译成二进制代码;
  2. 在文件的每一行末尾,如果当前消息位等于 1,则在换行符之前写入一个空格字符,否则不写入任何内容。

假定文件中的行数足以包含该消息。还假设文件中最初没有以空格结尾的单行(用于明确解码)。

关于代码的一些注释:

  1. 代码中没有错误处理以简化程序。没有错误处理,而是使用了很多断言;
  2. strdup我为and编写了自己的对应物strrev,因为它们都不包含在标准中。我分别命名了我的实现duplicateString,reverseString因为名称str*是由标准保留的;
  3. 我检查输入参数的有效性以防止使用内部 API 出错;
  4. 所有函数都被声明为静态的,因为它们在同一个翻译单元中。
  5. 输入文件以文本模式打开,而临时文件始终以二进制模式 ( w+b) 打开。这会导致一些问题吗?

编译器

我正在使用带有以下键的 Clang:

-std=c11
-Weverything
-Wpedantic
-fsanitize=address
-D_CRT_SECURE_NO_WARNINGS

编译器只发出一个警告:

warning: implicit conversion turns floating-point number into integer: 'double' to 'size_t' (aka 'unsigned int') [-Wfloat-conversion]
        const size_t newCapacity = ceil(s->capacity * DYNAMIC_STRING_GROW_FACTOR);

我看不出修复它的意义。

静态代码分析器

我还用 CppCheck 分析器检查了代码,它给出了一个错误:“内存泄漏:函数 stringToBinary 中的 ret ”:

char *stringToBinary(const char *s)
{
    assert(s);
    assert(strlen(s) > 0);

    char *ret = calloc((strlen(s) + 1) * CHAR_BIT + 1, 1);
    assert(ret);

    for (size_t i = 0; s[i] != '\0'; i++)
        strcat(ret, charToBinary(s[i]));

    return strcat(ret, charToBinary('\0'));
}

但这似乎是一个误报,因为它res是在函数中释放的hideMessage:

char *msgBinary = stringToBinary(msg);
...
free(msgBinary);

实际上,代码是:

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

#define DYNAMIC_STRING_GROW_FACTOR 1.5

typedef struct DynamicString {
    char *s;
    size_t capacity;
    size_t length;
} DynamicString;

static void  createDynamicString(DynamicString *, size_t);
static void  destroyDynamicString(DynamicString *);
static void  increaseDynamicStringCapacity(DynamicString *);
static void  appendCharToDynamicString(DynamicString *, char);
static void  hideMessage(FILE *, const char *);
static char *extractMessage(FILE *);
static void  copyFile(FILE *, FILE *);
static char *stringToBinary(const char *);
static char *charToBinary(char);
static char  charFromBinary(const char *);
static char *reverseString(char *);
static char *duplicateString(const char *s);

int main(void)
{
    FILE *file = fopen("file.txt", "r+");
    assert(file);

    hideMessage(file, "hello, world");

    char *msg = extractMessage(file);
    assert(msg);
    puts(msg);

    free(msg);
    fclose(file);
}

/* The HIDEMESSAGE function
 *
 * The function hides text message into the file by that way: 1) the message
 * converts to binary; 2) each bit of the converted message writes at the end
 * of each file's line (right before the new-line character): if the bit
 * is 1 then a space (' ') appends to the line otherwise it's nothing appends
 * to the line.
 *
 * Assumes the the file does not contain any spaces right before new-line
 * characters. Also assumes that the file has enough lines for storing
 * the message.
 */
void hideMessage(FILE *f, const char *msg)
{
    assert(f);
    assert(msg);
    assert(strlen(msg) > 0);

    char *msgBinary = stringToBinary(msg);
    assert(msgBinary);

    FILE *tempFile = tmpfile();
    assert(tempFile);

    for (int ch, i = 0; (ch = fgetc(f)) != EOF;) {
        if (msgBinary[i] && (ch == '\n'))
            if (msgBinary[i++] == '1')
                fputc(' ', tempFile);

        fputc(ch, tempFile);
    }

    copyFile(f, tempFile);

    free(msgBinary);
    fclose(tempFile);
}

/* The EXTRACTMESSAGE function
 *
 * The function extracts message hidden by the HIDEMESSAGE function from
 * the input file and returns a pointer to heap-allocated string which
 * contains the message.
 */
char *extractMessage(FILE *f)
{
    assert(f);

    DynamicString msgBuffer;
    createDynamicString(&msgBuffer, 128);

    char charInBinary[CHAR_BIT + 1] = {0};

    for (int prevCh = 0, ch, i = 0; (ch = fgetc(f)) != EOF; prevCh = ch) {
        if (ch == '\n')
            charInBinary[i++] = (prevCh == ' ') ? '1' : '0';

        if (i % CHAR_BIT == 0 && i != 0) {
            if (!strcmp(charInBinary, charToBinary('\0')))
                break;

            i = 0;
            appendCharToDynamicString(&msgBuffer, charFromBinary(charInBinary));
        }
    }

    char *ret = duplicateString(msgBuffer.s);
    assert(ret);

    destroyDynamicString(&msgBuffer);
    return ret;
}

/* The CREATEDYNAMICSTRING function
 *
 * The function initializes a DynamicString passing by the first argument.
 * The initial capacity of the string is passing by the second argument.
 * Capacity is the max length of the string. At the same time length is
 * current length of the string. Thus the function allocates capacity + 1
 * bytes for the string (considering the null-character).
 *
 * The input pointer to DynamicString struture should be a valid pointer and
 * capacity should be greater than 0.
 */
void createDynamicString(DynamicString *ret, size_t capacity)
{
    assert(ret);
    assert(capacity > 0);

    ret->s = malloc(capacity + 1);
    assert(ret->s);

    ret->length = 0;
    ret->capacity = capacity;
}

/* The APPENDCHARTODYNAMICSTRING function
 *
 * The function appends a character to the input DynamicString. If capacity of
 * the string is not enough the function increases it.
 *
 * The input pointer to a DynamicString should be a valid pointer as well as
 * its string buffer.
 */
void appendCharToDynamicString(DynamicString *s, char c)
{
    assert(s);
    assert(s->s);

    if (s->length == s->capacity)
        increaseDynamicStringCapacity(s);

    s->s[s->length++] = c;
    s->s[s->length] = '\0';
}

/* The INCREASEDYNAMICSTRINGCAPACITY function
 *
 * The function increases capacity of the input DynamicString. Grow factor
 * is sets by a macro constant DYNAMIC_STRING_GROW_FACTOR.
 *
 * The input pointer to a DynamicString struture should be a valid pointer
 * as well as its string buffer.
 */
void increaseDynamicStringCapacity(DynamicString *s)
{
    assert(s);
    assert(s->s);

    const size_t newCapacity =  ceil(s->capacity * DYNAMIC_STRING_GROW_FACTOR);

    s->s = realloc(s->s, newCapacity + 1);
    assert(s->s);

    s->capacity = newCapacity;
}

/* The DESTROYDYNAMICSTRING function
 *
 * The function destroys the input DynamicString. It frees the string buffer
 * of the input DynamicString.
 *
 * The input pointer to a DynamicString should be a valid pointer as well as
 * its string buffer.
 */
void destroyDynamicString(DynamicString *s)
{
    assert(s);
    assert(s->s);

    free(s->s);
}

/* The COPYFILE function
 *
 * The function copies all the contents of src to dest. Both arguments should
 * be valid pointers. dest should be open for writing, src should be open
 * for reading. The function does not close the files. The both file cursor
 * position sets to the beginning.
 */
void copyFile(FILE *dest, FILE *src)
{
    assert(dest);
    assert(src);

    rewind(dest);
    rewind(src);

    for (int ch; (ch = fgetc(src)) != EOF;)
        fputc(ch, dest);

    rewind(dest);
    rewind(src);
}

/* The CHARFROMBINARY function
 *
 * The function converts the input string returned by the CHARTOBINARY function
 * to a character.
 *
 * The input string should be a valid null-terminated string and its length
 * should be greater 0.
 *
 * charFromBinary(charToBinary(c)) == c
 */
char charFromBinary(const char *s)
{
    assert(s);
    assert(strlen(s) > 0);

    char ret = 0;
    unsigned int p = 1;

    for (size_t i = strlen(s); i-- > 0; p *= 2)
        if (s[i] == '1')
            ret += p;

    return ret;
}

/* The STRINGTOBINARY function
 *
 * The function converts the input string to binary form and returns a pointer
 * to heap-allocated null-terminated string. Null-terminator of the input
 * string also converts to binary form and appends to the result. The caller
 * should free memory allocated for the output string.
 *
 * The input string should be a valid null-terminated string and its length
 * should be greater 0.
 *
 * stringToBinary("cat") => "01100011011000010111010000000000"
 * stringToBinary("dog") => "01100100011011110110011100000000"
 * stringToBinary("R\0") => "0101001000000000"
 */
char *stringToBinary(const char *s)
{
    assert(s);
    assert(strlen(s) > 0);

    char *ret = calloc((strlen(s) + 1) * CHAR_BIT + 1, 1);
    assert(ret);

    for (size_t i = 0; s[i] != '\0'; i++)
        strcat(ret, charToBinary(s[i]));

    return strcat(ret, charToBinary('\0'));
}

/* The CHARTOBINARY function
 *
 * The function converts value of the input character to binary form and
 * returns a pointer to the static null-terminated string which contains
 * the result. The result contains leading zeroes.
 *
 * charToBinary(100) => "01100100"
 * charToBinary('A') => "01000001"
 * charToBinary('\0') => "00000000"
 */
char *charToBinary(char c)
{
    static char ret[CHAR_BIT + 1];

    memset(ret, '0', sizeof ret);
    ret[sizeof ret - 1] = '\0';

    for (size_t i = 0; c; i++) {
        ret[i] = (c % 2) ? '1' : '0';
        c /= 2;
    }

    return reverseString(ret);
}

/* The REVERSESTRING function
 *
 * The input string should be a valid pointer to a null-terminated string.
 * If the input string is empty the function does noting.
 *
 * reverseString("hello") => "olleh"
 * reverseString("a") => "a"
 * reverseString("") => "a"
 */
char *reverseString(char *s)
{
    assert(s);

    char *begin = s;
    char *end = s + strlen(s) - 1;

    for (; begin < end; begin++, end--) {
        const char t = *begin;
        *begin = *end;
        *end = t;
    }

    return s;
}

/* The DUPLICATESTRING function
 *
 * The function returns a pointer to a heap-allocated string, which is a
 * duplicate of the input string. The returned pointer can be passed to the
 * free function.
 */
char *duplicateString(const char *s)
{
    assert(s);
    assert(strlen(s) > 0);

    char *copy = malloc(strlen(s) + 1);
    assert(copy);

    return strcpy(copy, s);
}

更新

重写版本(Github Gist)(由CodeReview的 @avp 和 @Edward 审查)。

PS我用蹩脚的英文写评论,我希望至少有些东西会很清楚:)

c
  • 1 1 个回答
  • 10 Views

1 个回答

  • Voted
  1. Best Answer
    avp
    2020-06-13T04:08:56Z2020-06-13T04:08:56Z

    @eanmos,阅读 git 中的代码。在我看来,没关系,比第一个版本更好。

    一些笔记。

    1) Я не нашел, как кодируется конец сообщения при записи в файл. Такое впечатление, что его надо будет обнаруживать по нулевым байтам в конце, которые неявно появятся после записи в достаточно длинный файл.
    Может его стоит обозначить явно, например, записав 2 пробела в конец первой строки, идущей после сообщения (перед копированием остатка файла)?

    2) При разборе argc/argv в main() надо проверять количество аргументов для --hide
    Иначе свалится в fopen(argv[3], ...)

    3) Наверное стоит проверять assert(ferror(infile) == 0); после чтения EOF, а также проверить fclose(outfile) на предмет IO errors (реально запись на диск может и не поймать, но все же, раз уж вы результат malloc/realloc проверяете ..., то эту паранойю надо держать до конца -)).

    4) Очевидно, что после duplicateString(msgBuffer.s) в extractMessage() память надо либо освобождать, либо вместо ее вызова можно просто возвращать весь выделенный malloc/realloc-ами буфер
    (или если хотите фрагментировать память -), то можно вернуть
    realloc(msgBuffer.s, msgBuffer.length + 1))

    Еще одно.
    Мелочь, но умножение на 1.5 можно сделать в целых: n * 2 - n / 2 (или сдвигами, если компайлер плохо оптимизирует, то: (n << 1) - (n >> 1)).
    И еще мелочь. В принципе, в char может быть не 8 бит, а CHAR_BIT (из <limits.h>) и тогда 0x80, которую вы используете в цикле по битам, не играет.
    Я бы рекомендовал просто
    for (int j = 0; j < CHAR_BIT; j++) {if (encodeBit(infile, outfile, *msg & (1 << j)) == EOF) ... }
    (да, я понимаю, что тут порядок бит в байте обратный вашему, но мне он кажется более естественным).

    Возвращаясь к моему замечанию 3) о duplicateString(). В общем, я имел в виду, что она совсем не нужна.
    Но если бы она понадобилась, то в контексте использования struct DynamicString вместо вызова strlen() в ней надо использовать .length из структуры DynamicString и уж по любому memcpy() вместо strcpy(), поскольку уже в вызове malloc() размер строки известен.

    • 1

相关问题

Sidebar

Stats

  • 问题 10021
  • Answers 30001
  • 最佳答案 8000
  • 用户 6900
  • 常问
  • 回答
  • Marko Smith

    根据浏览器窗口的大小调整背景图案的大小

    • 2 个回答
  • Marko Smith

    理解for循环的执行逻辑

    • 1 个回答
  • Marko Smith

    复制动态数组时出错(C++)

    • 1 个回答
  • Marko Smith

    Or and If,elif,else 构造[重复]

    • 1 个回答
  • Marko Smith

    如何构建支持 x64 的 APK

    • 1 个回答
  • Marko Smith

    如何使按钮的输入宽度?

    • 2 个回答
  • Marko Smith

    如何显示对象变量的名称?

    • 3 个回答
  • Marko Smith

    如何循环一个函数?

    • 1 个回答
  • Marko Smith

    LOWORD 宏有什么作用?

    • 2 个回答
  • Marko Smith

    从字符串的开头删除直到并包括一个字符

    • 2 个回答
  • Martin Hope
    Alexandr_TT 2020年新年大赛! 2020-12-20 18:20:21 +0000 UTC
  • Martin Hope
    Alexandr_TT 圣诞树动画 2020-12-23 00:38:08 +0000 UTC
  • Martin Hope
    Air 究竟是什么标识了网站访问者? 2020-11-03 15:49:20 +0000 UTC
  • Martin Hope
    Qwertiy 号码显示 9223372036854775807 2020-07-11 18:16:49 +0000 UTC
  • Martin Hope
    user216109 如何为黑客设下陷阱,或充分击退攻击? 2020-05-10 02:22:52 +0000 UTC
  • Martin Hope
    Qwertiy 并变成3个无穷大 2020-11-06 07:15:57 +0000 UTC
  • Martin Hope
    koks_rs 什么是样板代码? 2020-10-27 15:43:19 +0000 UTC
  • Martin Hope
    Sirop4ik 向 git 提交发布的正确方法是什么? 2020-10-05 00:02:00 +0000 UTC
  • Martin Hope
    faoxis 为什么在这么多示例中函数都称为 foo? 2020-08-15 04:42:49 +0000 UTC
  • Martin Hope
    Pavel Mayorov 如何从事件或回调函数中返回值?或者至少等他们完成。 2020-08-11 16:49:28 +0000 UTC

热门标签

javascript python java php c# c++ html android jquery mysql

Explore

  • 主页
  • 问题
    • 热门问题
    • 最新问题
  • 标签
  • 帮助

Footer

RError.com

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

帮助

© 2023 RError.com All Rights Reserve   沪ICP备12040472号-5