0

我是一般的新程序员,我现在已经开始使用 c。我正在尝试解码 IDEv3 mp3 标签,但遇到了各种问题。当我使用 fread() 和 strncpy() 命令时,我注意到两者都需要将 \n 字符作为结束参考点。(也许我错了,这只是一个观察)

当我打印输出时,它们会产生一个不可读的字符。作为解决该问题的解决方案,我使用 fread() 4 个字节而不是 3 个,以便生成 (8)\n 个字符(整个字节),第二步我使用 strncpy() 和 3 个字节分配然后我用于打印的内存。理论上,当我使用 fread() 时,我不应该遇到这个问题。

代码示例:

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

typedef struct{
  unsigned char header_id[3]; /* Unsigned character 3 Bytes (24 bits) */
}mp3_Header;

int main (int argc, char *argv[]) {

mp3_Header first;
unsigned char memory[4];

FILE *file = fopen( name.mp3 , "rb" );

if ( (size_t) fread( (void *) memory , (size_t) 4 , (size_t) 1 , (FILE *) file) !=1 ) {
  printf("Could not read the file\n");
  exit (0);
} /* End of if condition */

strncpy( (char *) first.header_id , (char *) memory , (size_t) 3);

printf ("This is the header_ID: %s\n", first.header_id);

fclose(file);

} /* End of main */
return 0;
4

4 回答 4

5

您对 '\n' 终止字符串的观察是不正确的。在 C 中,字符串需要以 0 字节 (\0) 结束。然而,一些函数,如 fgets(),应该从文件中读取行,将行尾的 \n 作为终止符。

您的代码的问题是 fread() 准备好二进制数据,并且不会尝试将该数据解释为字符串,这意味着它不会将 \0 放在末尾。但是字符串函数,比如strcpy,需要这个 0 字节来识别字符串的结尾。strncpy在复制 \0 后也停止,但它不会将更多字节放入接收字符串以防止缓冲区溢出。所以它会复制你的 3 个字节,但它不会将 \0 放在字符串的末尾,就像字符串比长度参数短时那样。

所以你应该做的是用一个你实际需要的 MORE 元素声明 header_id,然后在 strcpy 之后,将此额外元素设置为 \0。像这样:

strncpy( first.header_id , memory , 3);
first.header_id[3] = '\0';

请记住,3 个标头字节将转到数组元素 0..2,因此元素 3 需要终止符。当然,您需要声明 header_id[4] 以便为额外的 \0 留出空间。

另请注意,我省略了类型转换 - 如果您的类型仍然正确,则不需要它们。将数组传递给函数无论如何都会传递指向第一个元素的指针,因此无需将数组 header_id 转换为 in 中的指针strncpy( (char *) first.header_id , (char *) memory , (size_t) 3);

于 2013-12-29T23:37:44.013 回答
2

是的,C 字符串总是以空 (0x00) 字符结尾。理解这一点并正确编码是程序员的责任。

例如,如果您的 header_id 最多为 3 个可打印字符的字符串,则需要在该数组中分配 4 个字符以允许尾随 null。(并且您需要确保 null 确实存在。)否则, printf 将不知道何时停止,并将继续打印,直到找到 0 字节。

于 2013-12-29T23:36:34.960 回答
2

当你在缓冲区之间复制二进制数据时,你应该使用适当的函数来完成这项工作,比如memcpy()。因为您正在处理二进制数据,所以您必须确切地知道缓冲区的长度,因为没有空字符来指示数据的结尾。

要使其成为字符串,只需分配长度+1 缓冲区并将最后一个字节设置为 '\0',瞧,你有一个字符串。但是.. 您复制的二进制数据中可能已经有一个空字符,因此您应该在相信它确实是您想要的字符串之前进行一些完整性检查。像 \001 这样的东西对于 mp3 格式可能是无效的 id。但它可能是一个损坏的文件,你永远不知道你在处理什么。

于 2013-12-29T23:44:30.850 回答
1

有两种处理标题的正确方法。我假设 MP3 文件有一个 IDV3 标签,所以文件以“TAG”或“TAG+”开头。因此,您要读取的部分有 4 个字节。

a)您认为char *memory是 C“字符串”,并且 first.header_id 也是如此。然后这样做(省略其他所有内容以显示重要部分):

typedef struct{
  unsigned char header_id[5];
} mp3_Header;
char memory[5];

fread(memory, 4, 1, file);
memory[4]='\0';
strncpy(first.header_id, memory, 5)

在 fread 之后,你的记忆是这样的:

   0    1    2    3    4
+----+----+----+----+----+
|  T |  A |  G |  + |  ? |
+----+----+----+----+----+

索引 4 处的第 5 个字节未定义,因为您只读取了 4 个字节。如果您在此字符串上使用字符串函数(例如printf("%s\n", memory));该函数不知道在哪里停止,因为没有终止 \0,并且 printf 将继续输出垃圾,直到它在计算机 RAM 中的某个位置找到下一个 \0。这就是为什么你memory[4]='\0'下一步要做的,所以它看起来像这样:

   0    1    2    3    4
+----+----+----+----+----+
|  T |  A |  G |  + | \0 |
+----+----+----+----+----+

现在,您可以使用 strncpy 将这 5 个字节复制到 first.header_id。请注意,您需要复制 5 个字节,而不仅仅是 4 个,还需要复制 \0。

(在这种情况下,您也可以使用 strcpy(不带 n) - 它在遇到的第一个 \0 处停止。但是现在,为了防止缓冲区溢出,人们似乎同意根本不使用 strcpy;相反,总是使用strncpy 并明确说明接收字符串的长度)。

b) 你把memory二进制数据当作二进制数据,把二进制数据复制到头部,然后把二进制数据转成字符串:

typedef struct{
  unsigned char header_id[5];
} mp3_Header;
char memory[4];

fread(memory, 4, 1, file);
memcpy(first.header_id, memory, 4)
first.header_id[4]='\0';

在这种情况下,内存末尾永远不会有 \0。所以现在使用 4 字节数组就足够了。在这种情况下(复制二进制数据),您不使用 strcpy,而是使用 memcpy。这仅复制 4 个字节。但是现在,first.header_id没有结束标记,所以你必须明确地分配它。如果您不是 100% 清楚,请尝试像我上面所做的那样绘制图像。

但永远记住:如果你使用像'+'这样的操作符,你就不会在字符串上工作。您处理单个字符。在 C 中,作为一个整体处理字符串的唯一方法是使用 str* 函数。

于 2013-12-30T16:01:59.837 回答