Why is strncpy in my for loop uneffective? - c

I'm trying to split a string into multiple little strings (of nb size).
But it doesn't work as i would like :
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <sys/types.h>
int main(int argc, char *argv[]) {
char *source = argv[1];
int taille=0;
int i=0;
int k;
int nb = 5;
char dest[strlen(source)/nb][nb];
while(i<strlen(source))
{
char *src = &source[i];
strncpy(dest[taille],src,nb);
i=i+nb;
taille++;
}
for(k = 0 ; k <8;k++)
{
printf("\t%s\n",dest[k]);
}
}
here is the trace :
jerome#debian:~/codeFTP/code/serveur$ ./a.out " bonjour cocoman, tu me donne20 balles?"
bonjour cocoman, tu me donne20 balles?
our cocoman, tu me donne20 balles?
ocoman, tu me donne20 balles?
n, tu me donne20 balles?
me donne20 balles?
onne20 balles?
0 balles?
les?
but the most weird is that if i take out the while ( or thefor, i've tried both), it works ( by takingoff the while i mean writing the strncpy 8 times with appropriates values instead of using loops).
Thanks for your interest.

strncpy does not null terminate strings. You'll need to do that yourself. When you printf the first one, printf never finds the null and starts printing off whatever happens to follow it in memory. Because they are in an array, the next byte it sees is the first byte of the next string. This continues until it reaches the last string, which is null terminated because strncpy actually saw the end of the source string.
You'll need to change your declaration to hold one more byte per string for the null character:
char dest[strlen(source)/nb][nb + 1];
And then manually null-terminate each substring after the copy:
dest[taile][nb] = 0;
I'm not sure why unrolling the loop worked - there was probably a slight change in your other logic when you rewrote it.
Edited to add: Also, as Gopi said in their answer, your math for finding the number of strings rounds down. If the string length ins't a perfect multiple of nb, then your array is too small and you are invoking undefined behavior. The simplest solution is to add one to that dimension too. Your loop is safe because it is based on strlen, not the number of substrings you computed.
char dest[strlen(source)/nb + 1][nb + 1];

There is a potential problem with
strlen(source)/nb as index and this will have minimum rows compared to strlen(source) and you are doing the below
while(i<strlen(source))
{
char *src = &source[i];
strncpy(dest[taille],src,nb);
i=i+nb;
taille++;
}
Now dest[taille] is bound to be array out of bound access and also make sure
strncpy() doesn't \0 terminate the strings.
Basic case : Pass some string of length less than 5 and you have UB. There are multiple such potential bugs in your code.

Related

copying an substring to string

i'm trying to get a 2 strings from the user and the second one will be the "needle" to copy to the first string
for example:
string 1 (user input): eight height freight
string 2 (user input): eight
output: EIGHT hEIGHT frEIGHT
for example i want to print: toDAY is a good DAY
having trouble copying multiple needles in stack
i have tried using while (*str) {rest of the function with str++}
i would love some explanation
#define _CRT_SECURE_NO_WARNINGS
#define N 101
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <conio.h>
void replaceSubstring(char* str, char* substr);
void main() {
int flag = 1;
char str[N], substr[N];
//char* str_ptr = &str, * substr_ptr = &substr; //creating pointer for the sake of while
while (flag) {
printf("\nEnter main text: ");
gets_s(str,N);
if (!str)
flag = 0;
printf("\nEnter sub-text: ");
gets_s(substr,N);
if (!str)
flag = 0;
replaceSubstring(str, substr);
printf("%s",str);
}
printf("\nExited. (press any key to exit)");
}
void replaceSubstring(char* str, char* substr) {
int lensbstr;
str = strstr(str, substr);
_strupr(substr); //cnvrt to UPPERCASE
lensbstr = strlen(substr); //length of the mutual string
if (str)
strncpy(str, substr, lensbstr);
}
This looks like a programming exercise, so I’m not going to just give you the answer. However, I’ll give you a few hints.
Two big problems:
You don’t have a loop that would replace the second and later instances.
You are upper-casing the substring... not a copy of the substring. A second pass through replaceSubstring would only match the upper-case version of the substring.
A couple of small problems / style comments:
str is an array, so its value is always non-zero, so “if(!str)” is never true.
strncpy is almost never the right answer. It will work here, but you shouldn’t get in the habit of using it. Its behavior is subtle and is rarely what you want. Here it would be faster and more obvious to use memcpy.
You are upper-casing the substring and measuring its length even if you didn’t find it and so won’t need those results.
Although using int for flags works and is the traditional way, newer versions of the language have stdbool.h, the “bool” type, and the “true” and “false” constants. Using those is almost always better.
You appear to intend to stop when the user enters an empty string for the first string. So why do you ask for the second string in that case? It seems like you want an infinite loop and a “break” in the middle.

CS50 IDE: printf returns extra characters

I am having problems with the printf function in the CS50 IDE. When I am using printf to print out a string (salt in this code), extra characters are being output that were not present in the original argument (argv).
Posted below is my code. Any help would be appreciated. Thank you.
#include <cs50.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
int main(int argc, string argv[])
{
// ensuring that only 1 command-line argument is inputted
if (argc != 2)
{
return 1;
}
char salt[2];
for (int i = 0; i < 2; i++)
{
char c = argv[1][i];
salt[i] = c;
}
printf("the first 2 characters of the argument is %s\n", salt);
}
You are missing a string terminator in salt.
Somehow the computer needs to know where your string ends in memory. It does so by reading until it encounters a NUL byte, which is a byte with value zero.
Your array salt has exactly 2 bytes of space, and after them, random garbage exists which just happens to be next in memory after your array. Since you don't have a string terminator, the computer will read this garbage as well until it encounters a NUL byte.
All you need to do is include such a byte in your array, like so:
char salt[3] = {0};
This will make salt one byte longer, and the {0} is a shorthand for {0, 0, 0} which will initialize the contents of the array with all zerores. (Alternatively, you could use char salt[3]; and later manually set the last byte to zero using salt[2] = 0;.)
In your case, salt is at least one element shy of being a string, unless the argv[1] is only one element, it does not contain a null-terminator.
You need to allocate space to hold the null-terminator and actually put one there to be able to use salt as string, as expected for the argument to %s conversion specifier in case of printf().
Otherwise, the string related functions and operations, which essentially rely on the fact that there will be a null terminator to mark the end of the char array (i.e., mark the end of valid memory that can be accessed), will try to access past the valid memory which causes undefined behavior. Once you hit UB, nothing is guaranteed.
So, considering the fact that you want to use
"....the first 2 characters of the argument....."
you need to make salt a 3-element char array, and make sure that salt[2] contains a null-terminator, like '\0'.

Vector char strange chars

Anyone know how to copy to strings? Cause I used the function strcpy but when I print the result it show strange characters. I want to concatenate 'name' + '#' + 'e-mail'. With scanf I have to put the character null '\0'?
#include <stdio.h>
#include <string.h>
int main (){
char message[150];
char name[150];
char mail[150];
char result[150];
printf("Introduce name: \n");
scanf("%s",message);
printf("Introduce email \n");
scanf("%s",server);
strcpy(result,message);
result[strlen(result)]='#';
strcpy(&result[strlen(result)],server);
printf("RESULT: %s\n",result);
return 0;
}
result[strlen(result)]='#'; overwrites the NUL terminator introduced into result by strcpy(result,message);. So the result of a subsequent strlen is undefined.
A better solution is to use strncat, or you could get away with writing
char result[150] = {'\0'};
which will initialise the entire array.
But you still run the risk of overflowing your result array. You could use the safer strncpy to obviate that. Better still, use snprintf and have the C standard library perform the concatenation for you.

lack of understanding about sscanf usage

I would like to parse a specific line. So, I wrote the following piece of code in order to test the logic but I probably understand something wrongly :
typedef struct vers
{
char tu8UVersion[5];
char tu8UCommit[32];
}tst_prg_versions;
int main(int argc, char **argv)
{
tst_prg_versions lstVer;
char buf1[32];
char buf2[32];
char str[] = "BOARD-VERS-v1.0.0-git+9abc12345a";
sscanf(str, "BOARD-VERS-v%5s-git+%s", lstVer.tu8UVersion, lstVer.tu8UCommit);
printf("vers='%s'\n", lstVer.tu8UVersion);
printf("commit='%s'\n", lstVer.tu8UCommit);
sscanf(str, "BOARD-VERS-v%5s-git+%s", buf1, buf2);
printf("vers='%s'\n", buf1);
printf("commit='%s'\n", buf2);
return 0;
}
Once executed it returns :
vers='1.0.09abc12345a'
commit='9abc12345a'
vers='1.0.0'
commit='9abc12345a
Why the first vers is equals to 1.0.09abc12345a and not 1.0.0 ?
The first actually reads 1.0.0! Problem is, however, that tu8UVersion is not null-terminated, thus printf (not sscanf) prints beyound the field (doing so is undefined behaviour, however, as noted by sjsam) - which is immediately followed by tu8UCommit (does not necessarily have to be so, there could still be some fill bytes in between for alignment reasons!).
You need to either print 5 characters at most (%.5s in printf format string) or leave place for terminating the tu8UVersion with 0, as proposed in a comment already.
Something similar could have happened with your buffers, too. You are lucky that they appearently have been initialized to 0 already (probably because of compiled as debug version), which again does not necessarily have to happen. So with bad luck, you could have printed the whole rest of buf1 (having been left at garbage) and even beyond.
Why the first vers is equals to 1.0.09abc12345a and not 1.0.0 ?
Remember that you have
typedef struct vers
{
char tu8UVersion[5];
char tu8UCommit[32];
}tst_prg_versions;
I guess, there is a good chance the memory for tu8UVersion and tu8UCommit is contiguous. Since you have not null-terminated tu8UVersion when you do :
printf("vers='%s'\n", lstVer.tu8UVersion);
it goes on to print tu8UCommit and it stops because tu8UCommit is null terminated.
While sscanf seem the most sensible solution here you could also introduce some formatting :
char tu8UVersion[32];
/* version number can't get too big.
* So the first step is do allocated a
* reasonably - but not too - big size for it.
* So that you can be sure there are few empty bytes at the end.
*/
and then use a function to sanitize a string :
char* sanitized(char* ptr)
{
if(ptr[strlen(ptr)]!='\0') // include string.h for strlen
ptr[strlen(ptr)]='\0';
return ptr;
}
and print it like :
printf("vers='%s'\n", sanitized(lstVer.tu8UVersion));
Your problem has already been identified in the comments: You don't leave space for the terminating null character and the two strings are run together.
If you want to scan a version whose size you don't know beforehand, you can limit the characters to scan to decimal digits and points with %[.-9] or to everything except a hyphen with %[^-]. (The %[...] format is like %s, except that you must provide a list of valid characters in the brackets. A caret as first letter means that the string is made up of characters that are not listed. In other words, %s is short for %[^ \t\n]
When you scan a string, you should test the return value of sscanf to be sure that all items have been scanned correctly and contain valid values.
Here's a variant that scans version numbers of up to 11 letters:
#include <stdlib.h>
#include <stdio.h>
typedef struct vers
{
char tu8UVersion[12];
char tu8UCommit[32];
} tst_prg_versions;
int main(int argc, char **argv)
{
tst_prg_versions lstVer;
char str[] = "BOARD-VERS-v1.0.0-git+9abc12345a";
int n;
n = sscanf(str, "BOARD-VERS-v%11[^-]-git+%s",
lstVer.tu8UVersion, lstVer.tu8UCommit);
if (n == 2) {
printf("vers='%s'\n", lstVer.tu8UVersion);
printf("commit='%s'\n", lstVer.tu8UCommit);
} else {
puts("Parse error.");
}
return 0;
}

Need help finding bug, if string input is composed all of same character one output character is corrupt

reverser() reverses a cstring (not in place). 99% of the time it works but some input corrupts it for example it appears if aStr2[] is assigned a string made up of the same character it will have an error.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char* reverser(const char *str);
int main()
{
char aStr[] = "aaa";
char aStr2[] = "cccccc";
printf("%s %s", aStr, aStr2);
char* tmp = reverser(aStr2);//tmp now has garbage
printf("\n%s", tmp);
printf(" %s", aStr2);
return 0;
}
char* reverser(const char *str)
{
char* revStr = (char*)malloc(strlen(str));
int i;
for(i = strlen(str)-1; i >= 0; i--)
{
revStr[strlen(str)-1-i] = str[i];
}
return revStr;
}
Gives
aaa cccccc
cccccc9 cccccc
Process returned 0 (0x0) execution time : 0.068 s
Press any key to continue
Notice the 9 that shouldn't be there.
Change this malloc to strlen(str) + 1 , plus 1 for '\0'
char* revStr = (char*)malloc(strlen(str) + 1);
and after the for loop
revStr[strlen(str)+1] = '\0';
Your problem is that you don't put the string terminator in your reversed string. All strings in C are actually one extra character that isn't reported by strlen, and that is the character '\0' (or plain and simple, a zero). This tells all C functions when the string ends.
Therefore you need to allocate space for this extra terminator character in your malloc call, and add it after the last character in the string.
There are also a couple of other problems with your code, the first is that you should not cast the return of malloc (or any other function returning void *). Another that you have a memory leak in that you do not free the memory you allocate. This last point doesn't matter in a small program like the one you have here, but will be an issue in larger and longer running programs.
You haven't null-terminated your reversed string. You need to set the final index of revStr[] to 0.

Resources