C programming: strlcpy with len = 0 [closed] - c

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 4 years ago.
Improve this question
I'm just learning about programming using the C language.
Today, I'm trying to code my own strlcpy function and I am facing a problem.
To test my function I compare the results with the "official" function's ones. Everything works fine except... When I put 0 as the len arg.
The strcpy function seems to put a garbage character in the destination string and I really don't understand why.
Here is the function's prototype:
size_t strlcpy(char * restrict dst, const char * restrict src, size_t dstsize);
Thanks for your help!
Ok. I wanted to make a lot of tests, this is the reason why I'm calling the function inside of a loop.
Here is a part of my main function, testing the function:
do
{
/* Ask for first string */
printf("\nGive me a string (0 to stop): ");
gets(str);
/* Ask for a number */
printf("Now, give me a number please: ");
scanf("%d", &i);
while (getchar() != '\n');
/* I test with the "official function */
j = strlcpy(str2, str, i);
printf("Here is the expected result: %s\n", str2);
printf("Num returned: %d\n", j);
/* Now I test using my function */
j = ft_strlcpy(str3, str, i);
printf("Here is my result: %s\n", str3);
printf("Num returned: %d\n", j);
}while (str[0] != '0');
And here is the function I've coded:
unsigned int ft_strlcpy(char *dest, char *src, unsigned int size)
{
unsigned int cpt;
unsigned int i;
cpt = 0;
i = 0;
while (src[cpt] != '\0')
cpt++;
if (size == 0)
return (0);
while (i < cpt && i < (size - 1))
{
dest[i] = src[i];
i++;
}
dest[i] = '\0';
return (cpt);
}
In the function I'm not supposed to call any function from the standard library. My main is just here for testing.
The function prototype is given by my teacher, this is the reason why I don't respect the original one.
Sorry fort the time I needed to put my code here and thank you for your help.

could you explain me where the "garbage character" comes from? What does the function do to find this character and to put it in the string? Even if it is not supposed to be called with a 0 len value.
The manual does not say that strlcpy is not supposed to be called with a 0 len value, it only says that it isn't NUL-terminating the result if dstsize is 0, i. e. it copies no characters at all to dst.
Your impression that it would put a "garbage character" into the destination string most probably results from dst being uninitialized from the start, and you looking at the unchanged, uninitialized dst.

First of all, I suppose you mistype in "...strlen function seems to put a garbage..." (strlcpy should be instead of strlen, because strlen put nothing to string - size_t strlen(const char *s);).
So you have a question about strlcpy.
As referencess said about the third argument of strlcpy (as well as strncpy) determies number of characters to be copied from src to dst. So, in case of 0-size no data should be copied. Read documentation carefully - prototype is:
strlcpy(char *dst, const char *src, size_t size);
and explanation for size has words:
as long as size is larger than 0

There are several issues in your program.
First of all don't use gets(). It's not safe and moreover it has been obsoleted. Check this.
Instead use fgets(). Check this.
In this statement
printf("\nGive me a string (0 to stop): ");
0 to stop will actually not stop anything and execute all the statements below it until while loop checks the condition. May you want a put if condition, like this:
if (str[0] != '0') {
.....
.....
}
}while (str[0] != '0');
In this statement
printf("Now, give me a number please: ");
scanf("%d", &i);
....
....
j = ft_strlcpy(str3, str, i);
You are passing this number as the destination size to your ft_strlcpy() function. Assume your source string contains "123456789" string and destination string size is 5 and user has given number input as 100. In function ft_strlcpy(), for the given input
//cpt variable value would be 9
//size variable value would be 100
//initial value of i is 0
while (i < 9 && i < (100 - 1))
{
dest[i] = src[i]; //buffer overflow!!!!, this statement will execute 9 times and dest buffer size is 5
i++;
}
So, instead of taking the number of characters to be copy as input from user, you should give size of the destination buffer. Like this
j = ft_strlcpy(str3, str, sizeof(str3));
From strlcpy
strlcpy() copies up to dstsize - 1 characters from the string src to dst,
NUL-terminating the result if dstsize is not 0.
That means if destination buffer size is 0, no copy is performed.
Check this also.
Putting these all together, you can do:
#include <stdio.h>
#include <string.h>
unsigned int ft_strlcpy(char *dest, const char *src, unsigned int size) {
unsigned int i = 0, j = 0;
while (src[i] != '\0') {
if (size && (i < (size - 1))) {
dest[i] = src[i];
j++;
}
i++;
}
dest[j] = '\0';
return (i);
}
int main() {
char str[100], str3[100];
unsigned int j;
do {
/* Ask for first string */
printf("\nEnter a string (press only enter key to stop): ");
if (fgets(str, sizeof(str), stdin) == NULL) {
fprintf (stderr, "Failed to read input");
break;
}
/* If input is bigger than the size of buffer, discard the rest of input characters */
if (strchr(str, '\n') == NULL) {
int c;
while((c = getchar()) != '\n' && c != EOF)
/* discard the character */;
}
else {
/* Remove the trailing newline character */
str[strcspn(str, "\n")] = 0;
}
if (str[0] != '\0') {
j = ft_strlcpy(str3, str, sizeof(str3));
printf("Here is my result: %s\n", str3);
printf("Num returned: %d\n", j);
}
}while (str[0] != '\0');
return 0;
}

Related

Trying to check if a number is a palindrome through the use of strings [duplicate]

This question already has answers here:
How do I check if a number is a palindrome?
(53 answers)
Closed 5 years ago.
I am trying to check if an input number is a palindrome. I am doing it through strings rather than ints. So, I am taking in a string and reversing it into another string. However, when I use the string compare function it does not give me 0, stating that the strings are not the same. Even when I put in for example "1001", both the input and reverse strings displays 1001. I have figured it out with other methods but am trying to understand what is wrong with this one in specific.
#include <stdio.h>
#include <string.h>
int main(void)
{
char input[100];
char reverse[100];
int numLen = 0;
printf("Enter a number\n");
fgets(input, 100, stdin);
printf("The number is: %s\n", input);
numLen = strlen(input) - 1;
printf("Length of string is: %d\n", numLen);
for (int i = 0; i < numLen; i++)
{
reverse[i] = input[numLen - 1 - i];
if (i == numLen - 1)
{
reverse[i + 1] = '\0';
}
}
printf("The reverse number is: %s\n", reverse);
printf("The original number is: %s\n", input);
int result = strcmp(input, reverse);
printf("Result of strcmp gives us: %d\n", result);
if (strcmp(input, reverse) == 0)
{
printf("These numbers are palindromes\n");
}
else
{
printf("These numbers are not palindromes\n");
}
return 0;
}
The problem is you are not handling the strings properly. You should overwrite the '\n' with \0.
...
char input[100];
char reverse[100];
int numLen = 0;
printf("Enter a number\n");
fgets(input, 100, stdin);
printf("The number is: %s\n", input);
input[strcspn(input,"\n")]='\0'; // getting the length of the
// string without `\n`
// and overwriting with `\0`
numLen = strlen(input) ; // now you don't need to put the -1
printf("Length of string is: %d\n", numLen);
for (int i = 0; i < numLen; i++)
{
....
Apart from these two changes everything else remains the same. You were reversing it all right. And then you used strcmp right way. But the extra \n is removed in the code I have shown.
(still) Why it works?
Now to give you a better idea. You formed the reversed string alright. But the original string has \n within itself.
printf("The reverse number is: (%s)\n", reverse);
printf("The original number is: (%s)\n", input);
In the previous program you just do write these two lines. You will understand where you went wrong.
On giving input 1001Enter it gives this output.
The reverse number is: (1001)
The original number is: (1001
)
What is strcspn doing?
I have using strcspn function got the length without \n and overwriting it with \0.
0 1 2 3 4 5 --> indices
1 0 0 1 \n \0 --> strcspn(input,"\n") returns 4.
1 0 0 1 \0 \0 --> input[strcspn(input,"\n")]='\0'
You can do simply like this without the copying and everything.
Without extra memory - in place palindrome checking
bool checkPal(const char *s){
for(int i = 0, j= strlen(s)-1; i< strlen(s) && j>=0 ; i++)
if(s[i] != s[j])
return false;
return true;
}
int main(void)
{
char input[100];
char reverse[100];
printf("Enter a number\n");
if( fgets(input, 100, stdin) )
printf("The number is: %s\n", input);
input[strcspn(input,"\n")]='\0';
int numLen = strlen(input) ;
printf("Length of string is: %d \n", numLen);
printf("These numbers are %spalindromes\n", checkPal(input)?"not ":"");
return 0;
}
A more succinct way to write the checkPal() would be,
bool checkPal(const char *first){
const char *last = first + strlen(first);
while (first < last) {
if (*first++ != *--last) {
return false;
}
}
return true;
}
last points to the \0 character. Subtraction is necessary before we start doing comparison. To get a clear idea of what happens you have to know the precedence and few rules.
The first<last part is obvious. We are comparing till we reach a point where we first > last (For even length strings) or first = last (for odd length strings).
The if is a bit tricky. *first++ there are two operators involved. * (indirection) and ++(post increment).
And precedence of ++ is higher than de-reference *.
So *first++ will be - first is incremented. Then you might think that we are missing one character very first time but that's not the case. Value of a postfix expression is the value before we do first++. So now you have the first character.
Same way *--last will have the same effect except the value of the prefix expression is the value after the operation. So you are considering the last character.
If they matches we continue. first and last already contain the modified value. We repeat the same logic for rest of the characters in the smaller sub-string.
If a mismatch occurs then we return immediately. (Because it's not a palindrome).
Sorry, my bad. Try this:
#include <stdio.h>
#include <string.h>
// A function to check if a string str is palindrome
void isPalindrome(char str[])
{
// Start from leftmost and rightmost corners of str
int l = 0;
int h = strlen(str) - 1;
// Keep comparing characters while they are same
while (h > l)
{
if (str[l++] != str[h--])
{
printf("%s is Not Palindromen", str);
return;
}
}
printf("%s is palindromen", str);
}
// Driver program to test above function
int main()
{
isPalindrome("abba");
isPalindrome("abbccbba");
isPalindrome("geeks");
return 0;
}
Does this one work?
A variant, recursive version that has no more that the string as argument (or a copy of the original string)
int pal(char *s) {
int n = strlen(s);
if (n <= 1) return 1;
if (s[0] != s[n-1]) return 0;
s[n-1] = '\0';
return pal(++s);
}
return 0: not a palindrome, 1: is a palindrome
Note the string is altered, so you can call it this way if it's a problem (or if the string is created in a static area)
char *copy = malloc(strlen(string)+1); // string is original string
strcpy(copy, string);
int ispal = pal( copy );
printf("Is %s a palindrome\n", ispal ? "":"not");

Longest relation between two strings

I am trying to find the maximum overlap between two strings without using any advanced functions or pointers but I am only able to find the overlap in the beginning of the strings using this code.
#include <stdio.h>
#include <string.h>
int main (void) {
char string1[256], string2[256];
int len = 0;
printf ("Enter string1");
fgets (string1, sizeof (string1), stdin);
printf ("Enter string2");
fgets (string2, sizeof (string2), stdin);
while (strncmp (string1, string2, len) == 0 && len < strlen (string1)) {
len = len + 1;
}
printf ("%d\n", len - 1);
printf (strcat (string1, string2));
return 0;
}
For example: If we enter "axyz" and "343axyz" it should get an output equal to 4. However, if I enter the similarity in the beginning "This is ax" and "This isas" is giving me the correct output 7.
You need two for/while loops to compute the maximum overlap.
In first loop, you step through the elements of the first string.
In the second loop, you step through the elements of the second string.
Keep track of the overall maximum overlap in a variable.
Compare the maximum overlap between the part of the first string and part of the second string as the inner function of the two loops.
Something like:
int maxOverlap = 0;
for (char* s1 = string1; *s1 != '\0'; ++s1)
{
for (char* s2 = string2; *s2 != '\0'; ++s2)
{
int overlap = 0;
while (s1[overlap] != '\0' && s1[overlap] == s2[overlap])
{
++overlap;
}
if ( maxOverlap < overlap )
{
maxOverlap = overlap;
}
}
}

Program runs too slowly with large input - C

The goal for this program is for it to count the number of instances that two consecutive letters are identical and print this number for every test case. The input can be up to 1,000,000 characters long (thus the size of the char array to hold the input). The website which has the coding challenge on it, however, states that the program times out at a 2s run-time. My question is, how can this program be optimized to process the data faster? Does the issue stem from the large char array?
Also: I get a compiler warning "assignment makes integer from pointer without a cast" for the line str[1000000] = "" What does this mean and how should it be handled instead?
Input:
number of test cases
strings of capital A's and B's
Output:
Number of duplicate letters next to each other for each test case, each on a new line.
Code:
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <stdlib.h>
int main() {
int n, c, a, results[10] = {};
char str[1000000];
scanf("%d", &n);
for (c = 0; c < n; c++) {
str[1000000] = "";
scanf("%s", str);
for (a = 0; a < (strlen(str)-1); a++) {
if (str[a] == str[a+1]) { results[c] += 1; }
}
}
for (c = 0; c < n; c++) {
printf("%d\n", results[c]);
}
return 0;
}
You don't need the line
str[1000000] = "";
scanf() adds a null terminator when it parses the input and writes it to str. This line is also writing beyond the end of the array, since the last element of the array is str[999999].
The reason you're getting the warning is because the type of str[10000000] is char, but the type of a string literal is char*.
To speed up the program, take the call to strlen() out of the loop.
size_t len = strlen(str)-1;
for (a = 0; a < len; a++) {
...
}
str[1000000] = "";
This does not do what you think it does and you're overflowing the buffer which results in undefined behaviour. An indexer's range is from 0 - sizeof(str) EXCLUSIVE. So you either add one to the
1000000 when initializing or use 999999 to access it instead. To get rid of the compiler warning and produce cleaner code use:
str[1000000] = '\0';
Or
str[999999] = '\0';
Depending on what you did to fix it.
As to optimizing, you should look at the assembly and go from there.
count the number of instances that two consecutive letters are identical and print this number for every test case
For efficiency, code needs a new approach as suggeted by #john bollinger & #molbdnilo
void ReportPairs(const char *str, size_t n) {
int previous = EOF;
unsigned long repeat = 0;
for (size_t i=0; i<n; i++) {
int ch = (unsigned char) str[i];
if (isalpha(ch) && ch == previous) {
repeat++;
}
previous = ch;
}
printf("Pair count %lu\n", repeat);
}
char *testcase1 = "test1122a33";
ReportPairs(testcase1, strlen(testcase1));
or directly from input and "each test case, each on a new line."
int ReportPairs2(FILE *inf) {
int previous = EOF;
unsigned long repeat = 0;
int ch;
for ((ch = fgetc(inf)) != '\n') {
if (ch == EOF) return ch;
if (isalpha(ch) && ch == previous) {
repeat++;
}
previous = ch;
}
printf("Pair count %lu\n", repeat);
return ch;
}
while (ReportPairs2(stdin) != EOF);
Unclear how OP wants to count "AAAA" as 2 or 3. This code counts it as 3.
One way to dramatically improve the run-time for your code is to limit the number of times you read from stdin. (basically process input in bigger chunks). You can do this a number of way, but probably one of the most efficient would be with fread. Even reading in 8-byte chunks can provide a big improvement over reading a character at a time. One example of such an implementation considering capital letters [A-Z] only would be:
#include <stdio.h>
#define RSIZE 8
int main (void) {
char qword[RSIZE] = {0};
char last = 0;
size_t i = 0;
size_t nchr = 0;
size_t dcount = 0;
/* read up to 8-bytes at a time */
while ((nchr = fread (qword, sizeof *qword, RSIZE, stdin)))
{ /* compare each byte to byte before */
for (i = 1; i < nchr && qword[i] && qword[i] != '\n'; i++)
{ /* if not [A-Z] continue, else compare */
if (qword[i-1] < 'A' || qword[i-1] > 'Z') continue;
if (i == 1 && last == qword[i-1]) dcount++;
if (qword[i-1] == qword[i]) dcount++;
}
last = qword[i-1]; /* save last for comparison w/next */
}
printf ("\n sequential duplicated characters [A-Z] : %zu\n\n",
dcount);
return 0;
}
Output/Time with 868789 chars
$ time ./bin/find_dup_digits <dat/d434839c-d-input-d4340a6.txt
sequential duplicated characters [A-Z] : 434893
real 0m0.024s
user 0m0.017s
sys 0m0.005s
Note: the string was actually a string of '0's and '1's run with a modified test of if (qword[i-1] < '0' || qword[i-1] > '9') continue; rather than the test for [A-Z]...continue, but your results with 'A's and 'B's should be virtually identical. 1000000 would still be significantly under .1 seconds. You can play with the RSIZE value to see if there is any benefit to reading a larger (suggested 'power of 2') size of characters. (note: this counts AAAA as 3) Hope this helps.

Split string into array in C

Currently I'm trying to take a binary string, say 100101010, and split it into groups of three, so 100 101 010. Here's what I've written so far, for some reason it only prints the first group, 100 and then nothing after that.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(){
int i;
char *line = NULL;
free(line);
scanf("%ms", &line);
printf("%d\n", strlen(line));
for(i=0; i < strlen(line); ++i) {
if ( i % 3 == 0 ){
sprintf(line, "%c%c%c", line[i],line[i+1],line[i+2]);
printf(line);
}
}
}
sprintf(line, "%c%c%c", line[i],line[i+1],line[i+2]); writes your 3 characters into line, and so you overwrite your original string with your first group of 3. This means the next time through the loop i(4) is > strlen(line)(3) and so the loop stops.
Try:
/* Since 'line' and it's contents doesn't change in the loop we can
* avoid the overhead of strlen() calls by doing it once and saving the
* result.
*/
int len = strlen(line);
/* As mentioned in the comments, you could do
* for(i = 0; i < len; i+=3) and then you don't need the
* if (i%3) check inside the loop
*/
for(i=0; i < len; ++i) {
if ( i % 3 == 0 ){
/* This could be refactored to a loop
* or scanf() to a different string but I say scanf is overkill
* in this scenario...
*/
char buffer[4];
buffer[0] = line[i];
buffer[1] = line[i+1];
buffer[2] = line[i+2];
buffer[3] = '\0';
printf("%s\n", buffer);
// Or just use puts() since we're not really doing
// any formatting.
}
}
strlen(line) is reevaluated on each pass through the for loop, and you're changing the data that line points to inside the for loop by calling sprintf. Your sprintf makes line a 3-character string, hence you get only one trip through the loop in which i%3 is zero.

String comparison function

I am new to C and am trying to figure out and learn why my code isn't working. I understand that in C a string is basically an Array of each character. So I have been trying to search through the array to find the letter a and then print something if it is found. But my program keeps crashing every time I try to run it.
Here is my code:
#include <stdio.h>
void Display(char ch[]);
int main() {
char c[50];
printf("Enter String: ");
gets(c);
Display(c);
return 0;
}
void Display(char ch[]) {
int i;
for (i = 0; i < (sizeof(ch)); i++) {
if (strcmp(ch[i],"a") == 0) {
printf( "Yes");
}
}
}
When I run my program I enter a random string for example "fdas" and press enter. Then it crashes =\
Please remember I am new to C. I am a Java programmer if that helps with any explanations.
This is wrong
if(strcmp(ch[i],"a") == 0)
it should be
if (ch[i] == 'a')
and also, sizeof(ch) is not giving you the length of the string, for that you need strlen(), your Display() function should look like this to work
void Display(char *ch) {
size_t i;
size_t length;
if (ch == NULL)
return;
length = strlen(ch);
for (i = 0 ; i < length ; i++) {
if (ch[i] == 'a') {
printf( "Yes");
}
}
}
also, using gets() is unsafe, and deprecated, usefgets()
fgets(c, sizeof(c), stdin);
is better than gets(c) because it will prevent buffer overflow, note that I've used the sizeof operator in this case, because c is an array of char and the sizeof operator will give it's size in bytes, and since 1 char == 1 byte then it works.
In the case of the Display() function, it's not the same because there the sizeof operator will give the size of the type of ch, and since what you really need is the count of characters that ch points to, so you must use strlen() or compute the length yourself.

Resources