fread()中的空字符和c中的strncpy()

时间:2013-12-29 23:26:58

标签: c fread strncpy id3v2

我是一名新的程序员,现在我已经开始使用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 个答案:

答案 0 :(得分:5)

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

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

所以你应该做的是用一个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强制转换为strncpy( (char *) first.header_id , (char *) memory , (size_t) 3);中的指针。

答案 1 :(得分:2)

是的,C字符串总是以null(0x00)字符结尾。程序员有责任理解并适当编码。

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

答案 2 :(得分:2)

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

要使它成为一个字符串,只需分配长度+ 1缓冲区并将最后一个字节设置为'\ 0',瞧,你有一个字符串。但是..您复制的二进制数据中可能已经存在空字符,因此您应该先进行一些完整性检查,然后再将其视为您想要的字符串。类似\ 001的内容可能是mp3格式的无效ID ..但它可能是一个损坏的文件,你永远不知道你在处理什么。

答案 3 :(得分: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)

在恐惧之后,你的记忆如下:

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

未定义索引4处的第5个字节,因为您只读取4个字节。如果对此字符串使用字符串函数(例如printf("%s\n", memory));该函数不知道在哪里停止,因为没有终止\ 0,并且printf将继续输出垃圾,直到下一个\ 0它找到计算机RAM中的某个位置。这就是你接下来做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 *函数。