3

I tried to limit the number of n bytes copied to dest (here str1) by using strncpy(). The dest is big enough for the n bytes, but the output produced garbage when dest is smaller than the source (here argv[1]). This looks different, when i make dest large enough to hold the source.

Here's the code:

#include <stdio.h>                                                              
#include <string.h>                                                             
                                                                                
int main(int argc, char *argv[])                                                
{                                                                               
/* // Works when str1[?] is 20:                                                                    
  char str1[20];                                                                
  char str2[20]; */                                                             
                                                                                
  // produces garbage, str1 is smaller than argv[1] but big enough for the 4 bytes copied                                                            
  char str1[10];                                                                
  char str2[10];                                                                
                                                                                
  printf("argc = %i\n", argc);                                                  
  if (argc <= 1 || argc > 2){                                                   
    printf("Input Example:\n"                                                   
           "  %s SEGMENT:OFFSET\n"                                              
           "  %s 0100:F00A\n", argv[0], argv[0]);                               
    return 0;                                                                   
  }                                                                             
  printf("strlen(argv[1]) = %li, argv[1] = %s\n"                                
          , strlen(argv[1]), argv[1]);                                          
                                                                                
  // str1                                                                       
  strncpy(str1, argv[1], 4); // copying 4 bytes to str1                                                   
  printf("Copy only 4 Bytes -> sizeof(str1) = %li, "                            
         "strlen(str1) = %li, str1 = %s\n", sizeof(str1), strlen(str1), str1);  
                                                                                
  // str2                                                                       
  strncpy(str2, argv[1], 3); // copying 3 bytes to str2                                                   
  printf("Copy only 3 Bytes -> sizeof(str2) = %li, "                            
         "strlen(str2) = %li, str2 = %s\n", sizeof(str2), strlen(str2), str2);  
                                                                                
  return 0;                                                                     
}                      

The input of 0100:F00A produces:

./test.bin 0100:F00A
argc = 2
strlen(argv[1]) = 9, argv[1] = 0100:F00A
Copy only 4 Bytes -> sizeof(str1) = 10, strlen(str1) = 8, str1 = 0100�U
Copy only 3 Bytes -> sizeof(str2) = 10, strlen(str2) = 3, str2 = 010

Expected was just str1 = 0100.

I also do wonder why str2 is correct, its initial array size is as small as str1.

When i change

char str1[10] 

to

char str1[20]

and by doing so make it larger than the input of argv[1], then the output is correct:

./test.bin 0100:F00A
argc = 2
strlen(argv[1]) = 9, argv[1] = 0100:F00A
Copy only 4 Bytes -> sizeof(str1) = 20, strlen(str1) = 4, str1 = 0100
Copy only 3 Bytes -> sizeof(str2) = 20, strlen(str2) = 3, str2 = 010

It seems like strncpy is first copying everything to dest and then cutting away the rest after it. But is that the way how strncpy works? I assumed it is only copying what is need, the 4 bytes.

Coder
  • 197
  • 6
  • As you can see, the `printf` prints more than four bytes of output. Since you say you expected it to output "0100", that means that you were expecting it to print four bytes of output. How were you expecting `printf` to know to output only four bytes? – David Schwartz Jul 08 '22 at 23:55
  • @David Schwartz i did assume, that strncpy (dest, src, 4); copies only 4 bytes to dest and terminates it with a zero. – Coder Jul 09 '22 at 00:34

3 Answers3

6

from man page https://linux.die.net/man/3/strncpy

The strncpy() function is similar, except that at most n bytes of src are copied. Warning: If there is no null byte among the first n bytes of src, the string placed in dest will not be null-terminated.

ie in your case no null is placed in dest

pm100
  • 48,078
  • 23
  • 82
  • 145
4

From the GLIBC manual page for strncpy():

Warning: If there is no null byte among the first n bytes of src, the string placed in dest will not be null-terminated.

You are observing the effect.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157
1

For whatever it's worth, I tried compiling and running your program as-is on my PC (Windows/GCC 10.3.0) ... and, superficially, it "looked OK". I did NOT see the artifacts you described.

This example illustrates what's happening:

#include <stdio.h>                                                              
#include <string.h>                                                             
                                                                                
int main(int argc, char *argv[])                                                
{ 
  char *s;
  char test1[] = "AAAAAAAA";                                                                             
  char test2[] = "AAAAAAAA";                                                                             

  printf("BEFORE: strlen(test1)=%lli, sizeof(test1)=%lli, test1=%s...\n",
    strlen(test1), sizeof(test1), test1);

  strncpy(test1, "BBBB", 4);
  printf("AFTER: strlen(test1)=%lli, sizeof(test1)=%lli, test1=%s...\n",
    strlen(test1), sizeof(test1), test1);

  strncpy(test2, "BBBB", 6);
  printf("AFTER: strlen(test2)=%lli, sizeof(test2)=%lli, test2=%s...\n",
    strlen(test2), sizeof(test2), test2);

  return 0;
}

  - gcc -o x -g -Wall -pedantic x.c
x.c:37:3: warning: 'strncpy' output truncated before terminating nul copying 4 bytes from a string of the same length [-Wstringop-truncation]
   37 |   strncpy(test1, "BBBB", 4);
      |   ^~~~~~~~~~~~~~~~~~~~~~~~~

  - x 0100:F00A
segment=0100, strlen(segment)=4; offset=F00A, strlen(offset)=4
BEFORE: strlen(test1)=8, sizeof(test1)=9, test1=AAAAAAAA...
AFTER: strlen(test1)=8, sizeof(test1)=9, test1=BBBBAAAA...
AFTER: strlen(test2)=4, sizeof(test2)=9, test2=BBBB...

ADDITIONAL NOTES:

  • Some systems (the BSDs, Solaris, and others) provide strlcpy().

    It copies at most size-1 bytes to dest, always adds a terminating null byte, and does not pad the target with (further) null bytes.

  • Another way to parse your command line into "segment" and offset" might be to use strtok():

    ...
    if (argc != 2) {                                                   
      printf("USAGE: enter SEGMENT:OFFSET, e.g. \"0100:F00A\"\n");                               
      return 1;                                                                   
    } 
    
    if ((strlen(argv[1]) != 9) || (strchr(argv[1], ':') == NULL)) {
      printf ("This doesn't look like a segment::offset!\n");
      return 1;
    }
    
    s = strtok(argv[1], ":");
    if (s == NULL){                                                   
      printf("Illegal entry: unable to parse %s\n", argv[1]);                               
    } 
    else {
      strcpy(segment, s);
      strcpy (offset, strtok(NULL, ":"));
      printf("segment=%s, strlen(segment)=%lli; offset=%s, strlen(offset)=%lli\n",
        segment, strlen(segment), offset, strlen(offset));
    }
    
paulsm4
  • 114,292
  • 17
  • 138
  • 190