I’m trying to make some sort of simple console text editor program to get better with C. I’m having trouble with what I thought would be a somewhat simple task:

How do I get each line from my char* buffer, which contains all of my text, so I can output each line, including empty lines, with the correct line number in front of it.

I tried several different ways already, but none have stuck. I tried strtok(), which is what is currently pushed to my repo, and it ignores whitespace. I tried strchr() but did not have the slightest idea how that function worked and got an infinite loop. I tried doing my own function to create an array of lines but that lead to a segmentation fault which was not fixed by mallocing the array. I am at a loss here, I’m not sure what I can do.

Here is the repo: https://codeberg.org/Mister_Bones/txt-ed

Here is the offending code:

// Print contents of file
int print_file(char* buffer) {
	// Print a new line
	printf("\n");

	// Print each line with line number
	// Set first line
	int line_num = 1;

	// Get individual line from buffer
	char* line = strtok(buffer, "\n");

	// Loop through and print lines
	// TODO: Don't ignore whitespace
	while (line != NULL) {
		printf("%4d\t%s\n", line_num, line);
		line = strtok(NULL, "\n");
		line_num++;
	}

	return 0;
}
  • BlackRoseAmongThorns@slrpnk.net
    link
    fedilink
    arrow-up
    2
    ·
    2 days ago

    I’ll attempt to give a more hand wavey approach, but one that is hopefully easier to understand.

    First, implement:

    \\ find first index of `c` within string `str`.
    \\ returns true if found, sets `out` to result.
    \\ otherwise returns false.
    bool find(char* str, size_t len, char c, size_t* out)
    

    Now this can be used in a loop, where you supply a slice starting at the last line (or the beginning of the slice, on the first iteration), where len is the length of the original string minus the amount of characters consumed (index of the beginning of the slice, relative to the start of the original string).

    Note this requires calculating the string length beforehand, if this is a c-string (as in null terminated).

    This should be relatively simpler to understand and implement than what the standard library supplies, sadly the C standard library is not easy to use or very reliable, so you’ll mostly have to make do yourself.

    I hope this helped, I’m currently away from a computer so i can’t give a code example, good luck.

  • Alphenex53@programming.dev
    link
    fedilink
    English
    arrow-up
    2
    ·
    edit-2
    3 days ago

    It is not beginner friendly but it is optimized. It doesn’t allocate memory or whatever. I don’t expect you to understand all this but I did it for fun anyway.

    int print_file(const char* buffer)
    {
        // Validate the buffer
        if (!buffer || *buffer == '\0') return 1;
    
        // Prepare the first line prefix
        unsigned int line = 1;
        printf("%4d\t", line); // You could pre-format it if u want
    
        const char* cursor = buffer; // The pointer that points to the first char
        const char* linestart = cursor; // The start of the line
        char ch; // Character register
    
        while (true) {
            ch = *cursor++; // Read character THEN advance the cursor.
    
            // Check if the character is null or is newline or windows thing
    
            if (ch == '\0') {
                int linelength = cursor - linestart - 1; // Minus the null terminator
                printf("%.*s\n", linelength, linestart); // Print line using the length of string
                break;
            } else if (ch == '\n') {
                int linelength = cursor - linestart - 1; // Minus the newline
                printf("%.*s\n", linelength, linestart); // Print line using the length of string
                linestart = cursor;
    
                printf("%4d\t", ++line); // Print next line prefix
            } else if (ch == '\r') {
                continue; // Ignore the Windows thing
            }
        }
        
        return 0;
    }
    
      • Alphenex53@programming.dev
        link
        fedilink
        English
        arrow-up
        2
        ·
        edit-2
        2 days ago

        TBF it is not complicated but it does use the simplest form of pointer arithmetic and order of operation of (++var) or (*var++). Considering OP couldn’t write a basic version of this I did not want to put pressure on him. If you can understand it as a beginner good for you! You must remember that a lot of developers struggle to learn pointers in the first place for some reason. I blame AI.

        EDIT: Also I did not say this is advanced, just not beginner friendly

  • 93zm4@programming.dev
    link
    fedilink
    arrow-up
    3
    ·
    4 days ago

    Any chance you are running this on an OS with a “new line” character that isn’t “\n”? Windows for example could be “\r\n”. I tried on Linux and it worked, although I only grabbed the necessary parts, so if something else is breaking, I can’t say. Or perhaps I misunderstood the issue altogether. Here is what I see:

    The function:

    # code block
    int print_file(char* buffer) {
    	printf("print_file\n");
    
    	int line_num = 1;
    
    	char* line = strtok(buffer, "\n");
    
    	while(line != NULL) {
    		printf("%4d\t%s\n", line_num, line);
    		line = strtok(NULL, "\n");
    		line_num++;
    	}
    	return 0;	
    }
    

    And the output:

    # code block
    test file: test.txt 
    hello 123 123 123 
    asdf asdf asdf 
    0oijf0w0j s0w0wfjwef 
    0wefjfjwefjkwejijiowefjo
    
    output:
       1	hello 123 123 123 
       2	asdf asdf asdf 
       3	0oijf0w0j s0w0wfjwef 
       4	0wefjfjwefjkwejijiowefjo
    count: 4 lines | 0 words | 82 characters
    
    • oantby@lemmy.today
      link
      fedilink
      arrow-up
      1
      ·
      4 days ago

      It feels like your file may have been CRLF instead of just LF. strtok would eat an empty line if it was strictly delimited by the given delimiter. Removing empty tokens is part of its intentional behavior.

  • wicked@programming.dev
    link
    fedilink
    arrow-up
    2
    ·
    4 days ago

    Before trying to learn and use the standard library, I’d advise you to write a few simple functions yourself:

    1. void print_until(const char* str, char c) - print every character in str until you see c or null (Note: Don’t print the c or null, which should ensure that a str that only contains a \0 doesn’t crash.)
    2. const char* print_until_2(const char* str, char c) - as before, but return the pointer to where you found c or null
    3. void print_delimited_by(const char* str, char delimiter, char separator) - use print_until_2 in a while-loop, don’t print the delimiter, but do print a separator.

    For example, print_delimited_by("1;2;3",';',',') should print 1,2,3.

    Finally, void num_delimited_by(const char* str, char del) - like print_delimited_by, but add a number before every output, and use newline as the separator.

    These fundamental string exercises will give you a solid foundation for understanding oantby’s explanations about the standard library. Note that no memory allocation is necessary.

  • oantby@lemmy.today
    link
    fedilink
    arrow-up
    2
    ·
    edit-2
    4 days ago

    the strtok issue is, per the man page, that strtok returns only “non empty” tokens with your given delimiters.

    Given only one delimiter, the strchr approach is likely what I’d use. understanding strchr (and the similar strstr) is a pretty useful thing to have anyway, so it’s a good time to learn it. strchr makes no modifications and isn’t aiming to make tokens for you. It is looking for the first instance of a given byte in a C string and, provided it found it, returning you a pointer to that byte. as such, if it returns you something other than NULL, you’ve got a pointer to a byte. assuming you’re fine with the function modifying the buffer, you can convert that byte to a nul byte (\0), then print buffer, then a new line. You then advance your buffer pointer to the byte past the now-nul byte. Continue until strchr returns NULL, which is your last line - assuming the buffer is nul-terminated

    Edit: example, now that I’ve got a real keyboard:

    void strchr_loop(char *buf) {
        char* p;
        while ((p = strchr(buf, '\n')) != NULL) {
            *p = '\0';
            printf("Line = %s\n", buf);
            buf = p + 1;
        }
        printf("Last line = %s\n", buf);
    }
    
    • dr_robotBones@reddthat.comOP
      link
      fedilink
      arrow-up
      1
      ·
      3 days ago

      I’ve put together code that copies the buffer and then prints it exactly as it would appear in a text editor. Since I used malloc() to allocate space for the copy, I figured I would need to use a free(), but it seems I can’t do that. Is it fine to not free strings, or is there a special way to free them?

      • oantby@lemmy.today
        link
        fedilink
        arrow-up
        1
        ·
        edit-2
        3 days ago

        So there’s a couple errors I can see in there: one which causes your issue here, and one that will cause another painful issue, unless you’re lucky.

        1. malloc(strlen(buffer + 1)); is not the same as malloc(strlen(buffer) + 1);. Your allocation is actually one byte shorter than your input string. This means your strcpy will copy a byte past the allocated buffer. Only bad things can come out of that, and they may not be noticeable at first.
        2. The issue you’re actually describing here requires maybe a bit better of an understanding of pointers. malloc is giving you back an address - call it 0x0001 - and promises to have at least the available size you requested. You promise to at some point free that address. That address is stored into line. strchr returns a different address - one later in the string, specifically. Your loop continuously advances line to that new address plus one. So, at the end, you’ve been given an address, 0x0001, and you’re trying to free some other address - e.g. 0x10de. That’s why you can’t free; you’re freeing something other than what you malloc’d. You need to hold onto the actual address returned by malloc in another variable, and free that address specifically.

        For #1, I’d personally recommend taking a look at strdup instead of malloc + strcpy. It’s identical functionally, so I invite you to wholly understand all three functions and understand when/why they’re used, but when copying a string exactly, strdup is the way to go - and it removes the possibility of an errant + 1. As part of the understanding, understand that the result of strdup does still need to be given to free eventually.

        As a general note, to provide maybe some clarity on “is it fine to not free strings” - strings don’t really exist in C. That is, there’s not some special thing that the system sees as a string. They’re by convention - a string is a blob of non-'\0' bytes that ends with '\0'. malloc and free do not know or care what you did with the bytes you received, and whenever you see a man page say that it does something with a string (copy, search, etc.), you can confidently substitute string with “blob of non-nul bytes ending with a nul byte”. “string” just sounds nicer, and by convention, that’s what we know a “C string” to be.

    • dr_robotBones@reddthat.comOP
      link
      fedilink
      arrow-up
      1
      ·
      4 days ago

      I see , thank you for the example, I think I’ve come up with a way I can use strchr for my purposes.

      Also, I’ve heard of there being man pages for C library functions before, is there a way I can run the man command with a C library function from the linux terminal?

      • oantby@lemmy.today
        link
        fedilink
        arrow-up
        0
        ·
        3 days ago

        Most linux distributions either include C man pages by default or add them in when you install dev tools. I’d be surprised if man strchr didn’t already come up with something for you.

        If you’re going down this path, it’s worth recognizing the different meanings of the man sections - shell commands in section 1 means that e.g. man chown is going to show you shell instead of the C function you’ll see from man 2 chown. In the same vein, section 3 has library functions, which are sometimes more relevant for you than section 2’s syscalls.

        • dr_robotBones@reddthat.comOP
          link
          fedilink
          arrow-up
          1
          ·
          3 days ago

          This is very useful, thank you! It should be alot easier to figure out how these functions work without relying on modern search engines.