1

I am working on creating my own version of strncpy. My code seems to accept the input fine, but the program terminates after the input is entered. strncpy also seems to pad the copied function with nulls if it's shorter than the first - what is the point of that and how do I implement that in my code?

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

#define SIZE 50
#define STOP "quit"

char *copywords(char *str1, char *str2, int n);

int main(void) {
    char words[SIZE];
    char newwords[SIZE];
    int num;
    int i = 0;
    int j = 0;

    printf("Type a word, and the # of chars to copy, or type 'quit' to quit: ");

    fgets(words, SIZE, stdin);
    scanf_s("%d", &num);

    if (words == STOP) {
        printf("Good bye!\n");
        return 0; 
    }

    copywords(words, newwords, num);

    printf("The word was");
    puts(words);

    printf("and the copied word is");
    puts(newwords);
}

char *copywords(char *str1, char *str2, int n) {
    int i; 

    for (i = 0; i < n; i++) {
        str2[i] = str1[i];
    }
    return str2;
}
chqrlie
  • 131,814
  • 10
  • 121
  • 189
Markovnikov
  • 41
  • 4
  • 13

3 Answers3

1

The short answer is: DO NOT USE strncpy().

You can read why here: https://randomascii.wordpress.com/2013/04/03/stop-using-strncpy-already/

The semantics of strncpy are obscure, widely misunderstood and error prone. The size argument is the size of the destination array, not some limit to the number of characters to copy from the source. If the source string length is size or larger, the destination will not be null terminated and if it is shorter, the remainder of the destination will be filled with null bytes ('\0').

The reasons for these choices are historical: strncpy() was used to copy filenames into a memory structure for an archaic file system where filenames were limited in length.

The lack of null termination is so error prone that this function should never be used in production code, because the programmer or the maintainer will all too easily misunderstand the code's actual behavior and create bugs by modifying it.

If you must re-implement it as an assignment, you must implement the exact semantics. It you just want to have a handy string function that copies a string with truncation, choose a different name and probably a different argument order.

Here are examples of both:

char *strncpy_reimplemented(char *dest, const char *src, size_t n) {
    size_t i;
    for (i = 0; i < n && src[i] != '\0'; i++) {
        dest[i] = src[i];
    }
    while (i < n) {
        dest[i++] = '\0';
    }
    return dest;
}

char *pstrcpy(char *dest, size_t size, const char *src) {
    size_t i;
    if (size > 0) {
        for (i = 0; i < size - 1 && src[i] != '\0'; i++) {
            dest[i] = src[i];
        }
        dest[i] = '\0';
    }
    return dest;
}

Your copywords function has problems:

  • You do not null terminate the destination if the source string is longer than size-1
  • You dereference the source string beyond its length if it is shorter than size-1
  • You do not check if the value typed by the user is within the proper bounds for the source and the destination arrays.

You have further problems:

  • (words == STOP) does not check whether the string read by fgets() is quit. You must first remove the trailing newline from the buffer and use strcmp() to compare strings:

    words[strcspn(words, "\n")] = '\0';
    if (!strcmp(words, "quit")) {
        printf("Good bye!\n");
        return 0; 
    }
    

Here is a corrected and simplified version of your code:

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

char *copywords(char *dest, const char *source, size_t n);

int main(void) {
    char words[50];
    char newwords[50];
    int num;

    for (;;) {
        printf("Type a word, or type 'quit' to quit: ");
        if (scanf("%49s", words) != 1) {
            printf("Invalid input!\n");
            return 0; 
        }
        if (!strcmp(words, "quit")) {
            printf("Good bye!\n");
            return 0; 
        }
        printf("Type the # of chars to copy: ");
        if (scanf("%d", &num) != 1) {
            printf("Invalid input!\n");
            return 0; 
        }
        copywords(newwords, words, num);
        printf("The word was %s\n", words);
        printf("and the copied word is %s\n", newwords);
    }
}

char *copywords(char *dest, const char *source, size_t n) {
    size_t i;
    for (i = 0; i < n && source[i] != '\0'; i++) {
        dest[i] = source[i];
    }
    dest[i] = '\0';
    return dest;
}
chqrlie
  • 131,814
  • 10
  • 121
  • 189
  • "Padded with zeroes" – gosh. I didn't know and so never got used to *relying* on that behavior, but [look! there it is!](https://opensource.apple.com/source/Libc/Libc-167/gen.subproj/ppc.subproj/strncpy.c): `while (n > 0) { *s++ = '\0'; --n; }`. GNU C uses [`memset`](http://code.metager.de/source/xref/gnu/glibc/string/strncpy.c). And now I'll have to try to forget that they do so. – Jongware Nov 27 '16 at 22:26
  • Thank you! The code works fine except until "dest[i] = '\0'. I am using microsoft visual studio and I get the error identifier "i" is not defined. – Markovnikov Nov 28 '16 at 07:31
  • @RadLexus: it is a good idea to remember that if you have: `const char *src = "abc"; char dst[20480]; strncpy(dst, sizeof(dst), src);` then this is going to write 20480 bytes, even though 4 would be sufficient for most people most of the time. Do that a few times and the cost of rezeroing those extra bytes becomes significant. (And don't even think of using `strncat()` unless you can tell me what the length argument means without looking it up — hint: it is _not_ the size of the destination array.) – Jonathan Leffler Nov 28 '16 at 07:49
  • @JonathanLeffler: `strncat()` is not as bad as `strncpy()` since the `n` argument really is the maximum number of characters to copy from the source string, which is exactly the purpose of the OP. Yet I usually do not condone using it because it looks too similar to `strncpy()` which I definitely never allow. I favor safer utility functions that take the size of the destination array and truncate the copy or concatenation operations instead of overrunning the buffer or leaving it unterminated. – chqrlie Nov 28 '16 at 08:37
  • 1
    I think it is worse because the size is not the size of the target, unlike most other size-limited functions. If you know enough to use strncat safely, you know enough to use a faster function such as memmove instead. – Jonathan Leffler Nov 28 '16 at 08:40
  • @JonathanLeffler: it is not the size of the target, which should be obvious from the the order of the arguments: `n` is passed after the source pointer, not the destination pointer. The inconsistency and downright broken semantics of `strncpy()` is just so misleading! I am using `size_t pstrcpy(char *dest, size_t dest_size, const char *source);`. `size_t pstrcat(char *dest, size_t dest_size, const char *source);` and their limited counterparts with an extra argument for the maximum count.. Just like `snprintf()`, the return value can tell if truncation occurred: it would be `>= dest_size`. – chqrlie Nov 28 '16 at 08:50
  • 1
    I agree with the argument for `strncpy`, padding with a `'\0'`'s on a 1M string can be costly, but I'm a bit confused why `snprintf` is taking fire. Just as a gun doesn't kill, `snprintf` doesn't fail to *nul-terminate*, the programmer does. If the argument is limited to maintainability and future programmers being confused and not understanding its use -- well, that doesn't put much confidence in the intelligence of those in the future. I find `snprintf` quite well behaved, and not easily replaced when a detailed format string is required. All good discussion. – David C. Rankin Nov 28 '16 at 09:09
  • @chqrlie when I run the program it freezes up after a word is entered. – Markovnikov Nov 28 '16 at 11:46
  • @Markovnikov: the program does not hang, it is waiting for the user to input the number of characters. I modified the program to have 2 separate prompts and to repeat the process until an error or the user types `quit`. – chqrlie Nov 28 '16 at 14:18
  • Yes, you are correct. The program does not hang when I use Compile Online but I does hang when I use Microsoft Visual Studio - I receive this error: Exception thrown at 0x0FFB0BA0 (ucrtbased.dll) in lab1113problem7.exe: 0xC0000005: Access violation writing location 0x00B80000. – Markovnikov Nov 28 '16 at 23:06
0

Problem is you dont add terminating character at the end of your sting after copying

for (i = 0; i < n; i++)
{
    str2[i] = str1[i];
}
str2[i] = '\0';

Why are you returning str2 if you arent using it?

Also i think you need cant compare by == operator

Edit:

Full Code

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

#define SIZE 50
#define STOP "quit"

void copywords(char *str1, char *str2, int n);


int main(void)
{
char words[SIZE];
char newwords[SIZE];
int num;
int i = 0;
int j = 0;

printf("Type a word, and the # of chars to copy, or type “quit” to quit: ");
fgets(words, SIZE, stdin);
scanf("%d", &num);
copywords(words, newwords, num);
printf("The word was ");
puts(words);
printf("and the copied word is ");
puts(newwords);
return 0;

}

void copywords(char *str1, char *str2, int n)
{
int i;
for (i = 0; i < n; i++)
{
    str2[i] = str1[i];
}
str2[i] = '\0';
}
Sniper
  • 1,428
  • 1
  • 12
  • 28
0

Eh. I would NEVER recommend writing "clever" code in favor of "clear" code, but here's an example of abusing a for loop to implement strncpy():

char *my_strncpy(char *dst, const char *src, size_t n) {
    char *d;
    size_t i;
    for (i=0, d=dst; i<n; i++, *d++ = (*src == '\0') ? '\0' : *src++) { }
    return dst; 
}

Compound expressions, ternary logic that exploits short-circuiting, "putting everything in the for loop rather than in the body", it's got it all. Don't go there!

fearless_fool
  • 33,645
  • 23
  • 135
  • 217