如果没有换行符,则代码不返回行

Code not returning line if there is no newline character

提问人:CVB 提问时间:7/13/2023 最后编辑:chqrlieCVB 更新时间:7/14/2023 访问量:150

问:

几个月前,我开始用 C 语言编写代码。我编写了这段代码来返回文件中的每一行。它必须使用任何缓冲区大小,现在它确实如此,并且它应该返回文件中存在的每一行。但是,如果我的行末尾没有换行符,我的代码就不会返回任何内容。

到目前为止,我认为问题出在我的职能上。我可以看到,我可以将没有换行符的行放入行变量以及存储变量中。但是,在代码清理存储后,我相信它进入了条件,因此在返回我的行变量中的行之前结束了我的程序。我添加了这个条件作为终止循环的一种方式,所以如果我删除它,代码就会卡在无限循环上。我尝试了其他方法,我设法获得了所有行,但它仅适用于大于文件大小的缓冲区大小,我通过将变量更改为等于 来做到这一点。在这种情况下,对于较小的缓冲区,我会得到这些线,但它们会被“破坏”。get_next_lineif(stash[0] == '\0')mainwhileifind_nl(line) + (line[0] != '\0');

我正处于学习 C 语言的起步阶段,因此我将不胜感激任何改进此代码的输入。先谢谢你

这是程序:

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

#define BUFFER_SIZE 100

int find_nl(char *stash);
char *get_line(char *stash, char *line);
void ft_clean(char *stash);

char    *get_next_line(int fd)
{
    static char     stash[BUFFER_SIZE + 1];
    char            *line;
    int         n;
    int         i;

    if (fd < 0 || BUFFER_SIZE <= 0)
        return (NULL);
    i = 0;
    n = 0;
    line = NULL;
    while (!i)
    {
        if (!stash[0])
            n = read(fd, stash, BUFFER_SIZE);   
        if (n == -1)
            return (NULL);
        line = get_line(stash, line);
        i = find_nl(stash) + (line == NULL);
        if (stash[0] == '\0')
            return (NULL);
        ft_clean(stash);
    }
    return (line);
}

int find_nl(char *stash)
{
    size_t      i;

    if (stash == NULL)
        return (0);
    i = 0;
    while (stash[i])
    {
        if (stash[i] == '\n')
            return (1);
        i++;
    }
    return (0);
}

char    *get_line(char *stash, char *line)
{
    size_t  len;
    size_t  i;
    size_t  j;
    char    *nline;

    len = 0;
    j = 0;
    while (stash[len] && stash[len] != '\n')
        len++;
    if (line == NULL)
        i = 0;
    else
        while (line[i])
            i++;
    nline = (char *)malloc((len + i + 1) * sizeof(char));
    if (nline == NULL)
        return (NULL);
    while (line && line[j])
    {
        nline[j] = line[j];
        j++;
    }
    i = 0;
    while (i < len)
    {
        nline[j] = stash[i];
        i++;
        j++;
    }
    nline[j] = '\0';
    return (nline);
}

void    ft_clean(char *stash)
{
    size_t  stash_len;
    size_t  len;
    size_t  i;

    len = 0;
    stash_len = 0;
    i = 0;
    if (stash == NULL)
        return ;
    while (stash[len])
    {
        if (stash[len] == '\n')
        {
            len++;
            break ;
        }
        len++;
    }
    while (stash[stash_len] != '\0')
        stash_len++;
    while (i < stash_len - len + 1)
    {
        stash[i] = stash[i + len];
        i++;
    }
    stash[i] = '\0';
}

int main(void) {
    char *line;
    while ((line = get_next_line(0)) != NULL) {
        printf("[%s]\n", line);
        free(line);
    }
    return 0;
}

如果按如下方式调用,则其工作原理:

printf 'abc\n' | ./a

但以下内容不会输出任何内容:

printf 'abc' | ./a
c getline 文件描述符

评论

0赞 CVB 7/13/2023
@AndreasWenzel我编辑了帖子以添加包含
1赞 CVB 7/13/2023
@TomKarzes,我有义务仅使用低级系统调用
0赞 CVB 7/13/2023
@Gerhardh感谢您的输入,我初始化了变量
1赞 12431234123412341234123 7/13/2023
read()末尾后不添加字节。您必须确保它在那里或使用其他方法来检查它。'\0'
1赞 12431234123412341234123 7/13/2023
OT:请在所有地方使用相同的缩进样式(您的缩进样式与其他缩进样式不同)。请确保该示例是可编译的。main

答:

0赞 12431234123412341234123 7/14/2023 #1

不是直接的答案,但我认为有更好的方法。您当前的代码很难理解,不是很模块化和错误。

您正在使用和在您的代码中,通常这没有错,但是由于您有malloc()free()

该项目要求我只使用低级系统调用

你不能使用它们。

我建议在调用此函数时使用 a 并将指针传递给该变量,而不是在函数内部使用变量(在您的情况下为 for,并且可能更多,因为这是更好的实现所必需的)。这也将允许您同时(准)对多个文件使用此函数。staticget_next_line()stashstructstruct

像这样的东西:

struct File_T
{
  int fd;
  char buffer[BUFFER_SIZE];
  size_t currentLineEnd; //position of the first char after the current line
  size_t validChars;
};

void file_init(struct File_T *file, int fd);
const char *file_getNextLine(struct File_T *file);


void file_init(struct File_T *file, int fd)
{
    file->fd=fd;
    file->currentLineEnd=0; 
    file->validChars=0;
}

当您从文件中获取时,您可能会收到字节,但其本身不会在末尾添加 a,即 不填充字符串,而是填充可以包含任何数据的缓冲区。您需要使用 的返回值来检查读取的字符数。我建议,你读到最后一个有效字符之后(从上次你或0设置),你可以在读字符的数量。另请注意,返回的是类型而不是 .read()'\0'read()'\0'read()read()bufferread()validCharsread()ssize_tint

ssize_t n = read(file->fd,&file->buffer[file->validChars],BUFFER_SIZE-file->validChars-1); //-1 so we can always add a '\0'
if( n<0 )
  { /*do some error handling here*/ }
file->validChars+=n;

为了避免,您可以在 (直到你到达 ,之后你要么必须阅读更多内容,要么必须决定缓冲区中没有剩余空间,或者你阅读最后一行,那么你必须在位置之后使用下一个字符)并将其替换为 ,设置为 1 个字符,然后返回指向开头的指针。下次调用时,将所有有效内容从 till 移动到开头(使用或您自己的实现)并重复该过程。malloc()'\n'buffervalidCharsvalidChars'\0'currentLineEndfile_getNextLine()currentLineEndvalidCharsmemmove()

编辑:

如果需要处理任意长度的行,而不设置那么大,则可以为字符串分配新的内存来存储,而不使用 .由于您不想使用 ,因此可以使用 保留缓冲区。喜欢这个:BUFFER_SIZEcurrentLineEndmalloc()mmap()

char *buffer=mmap(NULL,newBufferSize,PROT_WRITE|PROT_READ,MAP_PRIVATE|MAP_ANONYMOUS,-1,0);

以后别忘了。munmap()

评论

0赞 12431234123412341234123 7/14/2023
@ikegami 但是分配的缓冲区和生成的字符串并不长(除了 +1 左右,因为他使用 +1 作为大小,我不确定)。当他想使用动态内存时,当他只想使用低级系统调用时,他必须使用 和 family。mmap()
0赞 12431234123412341234123 7/14/2023
@ikegami 你从哪里得到的?我没有读过那个要求。这是不可能的,至少在他使用这条线(作为单个字符串)的方式上是这样。若要存储 1000 字节的 C 字符串,需要至少 1001 字节的缓冲区。
0赞 12431234123412341234123 7/14/2023
@ikegami OP 要求仅使用低级系统调用。这意味着没有 ,也没有 。他可以使用(假设他使用类似 unix 的系统),但由于他没有,我也没有包括它。malloc()calloc()free()mmap()
0赞 12431234123412341234123 7/14/2023
@ikegami好的,如果这是 OP 的目标,我可以补充,但为此他必须回答评论(这不太可能)。
-1赞 Amerigo Scamardella 7/14/2023 #2

也许这个功能会有所帮助。它被称为 fgets,你可以用它来代替读取函数。如果你不能使用,只需复制它背后的想法。

函数实现

功能文档

定义:char *fgets(char *restrict s, int n, FILE *restrict stream);

fgets() 函数应将字节从流中读取到 s 指向的数组中,直到读取 n-1 个字节,或者读取<换行符>并将其传输到 s,或者遇到文件结束情况。然后,该字符串以 null 字节终止。成功完成后,fgets() 将返回 s。如果流位于文件末尾,则应设置流的文件结束指示符,并且 fgets() 应返回空指针。如果发生读取错误,则应设置流的错误指示器,fgets() 应返回空指针。

将其与此结合使用:

int get_cleaned_line(char str[], int maxlen)  //I never used read, idk fd  what it is
{             //you can use BUFFER_SIZE instead of maxlen
int len = -1;

    if (fgets(str, maxlen, stdin) != NULL)  //I think stdin it's like fd=0(input from keyboard)
    {               //whatever use the proper input file
    len = 0;
    while(str[len] != '\0')
        len++;
        if (len > 0 && str[len-1] == '\n') 
        {
            str[len-1] = '\0';
            len--;
        }

return len; //can be useful
}

我一年前刚学过C语言,我真的很讨厌弦乐,希望这有帮助。

评论

1赞 ikegami 7/14/2023
如果不允许,那么也不允许!此外,您的解决方案无法读取比 OP 专门处理的更长的行。freadfgetsmaxlen
0赞 Amerigo Scamardella 7/14/2023
@ikegami我没有从 OP 中读到任何这些限制。直到现在我才检查,他在评论中说了这句话,而不是内部问题。但他仍然可以从这个答案中得到启发,没有必要投反对票。我只是想帮忙,他要求提供任何意见,而不是完整的解决方案。
0赞 ikegami 7/14/2023
不过,它并没有回答这个问题。它应该被修复或删除。(有两个原因。你甚至没有解决其中之一。
0赞 Amerigo Scamardella 7/14/2023
@ikegami 给你实现,而不是让他写自己的代码想法,它也不是答案。当很明显他没有使用它时,你正在使用结构,原因与他不能使用高级函数的原因相同。他才刚刚开始。
0赞 ikegami 7/14/2023
这很有道理。
0赞 ikegami 7/14/2023 #3

事实证明,这相当复杂。这是我解决问题的尝试:

read_line.h:

#include <stdbool.h>

#define GET_LINE_BUFFER_SIZE 100

typedef struct {
   int fd;
   int error;
   int eof;
   size_t in_buf;
   char buffer[ GET_LINE_BUFFER_SIZE ];
} ReadLineData;

ReadLineData *ReadLine_new( int fd );
void ReadLine_init( ReadLineData *data, int fd );
void ReadLine_destroy( ReadLineData *data );
void ReadLine_free( ReadLineData *data );

// Returns -1 on error.    *line_ptr is set to NULL.  errno is set.
// Returns  0 on EOF.      *line_ptr is set to NULL.
// Returns +1 on success.  *line_ptr is set to a string to free.
int ReadLine_read_line( ReadLineData *data, char **line_ptr );

read_line.c:

#include <errno.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "read_line.h"

void ReadLine_init( ReadLineData *data, int fd ) {
   data->fd     = fd;
   data->error  = 0;
   data->eof    = 0;
   data->in_buf = 0;
}

// Returns NULL on error.  errno is set.
ReadLineData *ReadLine_new( int fd ) {
   ReadLineData *data = malloc( sizeof( ReadLineData ) );
   if ( data )
      ReadLine_init( data, fd );

   return data;
}

void ReadLine_destroy( ReadLineData *data ) {
   (void)data;  // Nothing to do.
}

void ReadLine_free( ReadLineData *data ) {
   ReadLine_destroy( data );
   free( data );
}

static char *find_lf( ReadLineData *data ) {
   char *p = data->buffer;
   for ( size_t n = data->in_buf; n--; ++p ) {
      if ( *p == '\n' ) {
         return p;
      }
   }

   return NULL;
}

// Returns false on error.  errno is set.
// Returns true on success.
static bool safe_size_add( size_t *acc_ptr, size_t to_add ) {
   if ( to_add > SIZE_MAX - *acc_ptr ) {
      errno = ENOMEM;
      return false;
   }

   return true;
} 

// Returns false on error.  errno is set.
// Returns true on success.
static bool move_to_line(
   ReadLineData *data,
   char **line_ptr,        // in-out. Set to NULL on error.
   size_t *line_len_ptr,   // in-out.
   size_t n
) {
   char *line      = *line_ptr;
   size_t line_len = *line_len_ptr;

   // Calculate new line size.
   // Protect against overflow.
   size_t new_line_len_p1 = line_len;
   if ( !safe_size_add( &new_line_len_p1, n ) )
      goto ERROR;
   if ( !safe_size_add( &new_line_len_p1, 1 ) )
      goto ERROR;

   // Enlarge the buffer.
   char *new_line = realloc( line, new_line_len_p1 );
   if ( !new_line )
      goto ERROR;

   line = new_line;

   // Copy from the buffer.
   memmove( line + line_len , data->buffer, n );
   line_len += n;
   line[ line_len ] = 0;

   // Remove from the buffer.
   data->in_buf -= n;
   memcpy( data->buffer, data->buffer + n, data->in_buf );

   *line_ptr     = line;
   *line_len_ptr = line_len;
   return true;

ERROR:
   free( line );
   *line_ptr = NULL;
   return false;
}

// Returns -1 on error.    *line_ptr is set to NULL.  errno is set.
// Returns  0 on EOF.      *line_ptr is set to NULL.
// Returns +1 on success.  *line_ptr is set to a string to free.
int ReadLine_get_line( ReadLineData *data, char **line_ptr ) {
   *line_ptr = NULL;
   size_t line_len = 0;

   if ( data->eof )
      return 0;

   while ( 1 ) {
      if ( data->in_buf ) {
         char *lf = find_lf( data );
         if ( lf )
            return move_to_line( data, line_ptr, &line_len, lf - data->buffer + 1 ) ? +1 : -1;

         // We didn't find a LF, so the whole buffer is part of the line.
         if ( !move_to_line( data, line_ptr, &line_len, data->in_buf ) )
            return -1;
      }

      // We need to read more.
      ssize_t bytes_read = read( data->fd, data->buffer, GET_LINE_BUFFER_SIZE );
      if ( bytes_read < 0 ) {
         data->eof   = 1;
         data->error = 1;
         free( *line_ptr );
         *line_ptr = NULL;
         return -1;
      }

      if ( bytes_read == 0 ) {
         data->eof = 1;
         return line_len ? +1 : 0;
      }

      data->in_buf = bytes_read;
   }
}

程序:

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

#include "read_line.h"

int main( void ) {
   ReadLineData data;
   ReadLine_init( &data, 0 );

   while ( 1 ) {
      char *line;
      int rv = ReadLine_read_line( &data, &line );
      if ( rv < 0 )
         err( EXIT_FAILURE, "read_line" );
      if ( rv == 0 )
         break;

      printf( "[%s]\n", line );
      free( line );      
   }

   ReadLine_destroy( &data );
}

它使用 、 和 。如果你想避免这些,前两个很容易自己重新实现。 重新实现会更棘手,并且需要类似 .这是留给读者的练习。memcpymemmovereallocreallocmmap