For loop is running too often - c

I have a for loop which should run 4 times but is running 6 times.
Could you please explain the behaviour?
This is strange because stringarr1 is not changed.
Edit: I want to remove all '!' from my first string and want to save the letters in a second string.
#include <stdio.h>
#include <math.h>
#include <string.h>
int main(){
char stringarr1[] = "a!bc";
char stringarr2[] = "";
printf("%d\n", strlen(stringarr1)); // lenght --> 4
for (size_t i = 0; i < strlen(stringarr1); i++)
{
printf("i: %d\n", i);
if (stringarr1[i] != '!') {
stringarr2[strlen(stringarr2)] = stringarr1[i];
printf("info: != '!'\n");
}
}
}

You are overrunning the buffer for stringarr2 (length 1), which is in this case corrupting the memory-adjacent stringarr1, causing the string length to change by overwriting its nul terminator.
Then because you are reevaluating the string length on each iteration, the loop will run for a non-deterministic number of iterations - in your case just 6, but it could be worse; the behaviour you have observed is just one of several possibilities - it is undefined.
Apart from correcting the buffer length for stringarr2, it is best practice to evaluate loop-invariants once (although in this case the string length is not invariant due to a bug). So the following:
const size_t length = strlen( stringarr1 ) ;
for( size_t i = 0; i < length; i++ )
{
...
will run for 4 iterations regardless of the buffer overrun bug because the length is not reevaluated following the corruption. Re-evaluating loop-invariants can lead to very slow code execution.

Your code can run any number of times. You write beyond the end of stringarr2 so you may be smashing the stack and overwriting local variables. What you meant to do is probably something like this:
#include <stdio.h>
#include <math.h>
#include <string.h>
int main(){
char stringarr1[] = "a!bc";
char stringarr2[10];
int len = strlen(stringarr1);
printf("%d\n", len); // lenght --> 4
for (size_t i = 0; i < len; i++)
{
printf("i: %d\n", i);
if (stringarr1[i] != '!') {
stringarr2[len] = stringarr1[i];
printf("info: != '!'\n");
}
}
}
Like others said, it is not really clear what you are trying to accomplish here. But in C, a declaration like char s[] = "string" only allocates enough memory to store whatever is on the right hand side of the assignment. If that is an empty string like in your case, only a single byte is allocated, to store the end of string 'null' character. You need to either explicitly specify, like I did, the number of bytes to allocate as the array size, or use dynamic memory allocation.

The problem is that you're writing past the end of stringarr2. This triggers undefined behaviour.
To fix this, you need to allocate sufficient memory for stringarr2.

First, we must allocate the string to be long enough.
char stringarr1[] = "a!bc";
//save this in a variable beforehand because strlen loops over the string every time it is called
size_t len = strlen(stringarr1);
char stringarr2[1024] = { 0 };
{ 0 } initializes all characters in the string to 0, which means the last one will always be a null terminator after we add characters. This tells C string functions where the string ends.
Now we can put stuff in there. It seems like you're trying to append, so keep a separate iterator for the 2nd string. This is more efficient than calling strlen every loop.
for(size_t i = 0, j = 0; i < len; i++){
printf("i: %d\n", i);
if (stringarr1[i] != '!') {
stringarr2[j++] = stringarr1[i];
printf("info: != '!'\n");
}
}

Related

CS50: pset2 / initials:- I've got code that works but I feel like I am taking a shortcut with setting my array size

So I am working away on the 'less comfortable' version of the initials problem in CS50, and after beginning with very verbose code I've managed to whittle it down to this:
#include <cs50.h>
#include <ctype.h>
#include <stdio.h>
#include <string.h>
int c = 0;
int main(void)
{
string name = get_string();
int n = strlen(name);
char initials[10];
// first letter is always going to be the first initial
initials[0] = name[0];
// count through letters looking for spaces + add the first letter after a
// space to the initials array
for (int j = 0; j < n; j++)
{
if (name[j] == 32)
{
c += 1;
initials[c] += name[j+1];
}
}
// print out initials
for (int k = 0; k <= c; k++)
{
printf("%c", toupper(initials[k]));
}
printf("\n");
}
As it stands like that it passes, but I feel like I am copping out a little cos I just pick [10] out of the air for the initial array size which I know isn't good practice. To make it a little 'better' I've tried to run a 'for' loop to iterate through the name string and add up the number of spaces. I then want to make the array [spaces + 1] as if there are 2 spaces then there will be 3 initials. The code I am trying for that is:
string name = get_string();
int n = strlen(name);
for (int i = 0; i < n; i++)
{
if (name[i] == 32)
{
spaces +=1;
}
}
The thought is that I then make 'char initials[spaces + 1]' on the next line, but even before I can do that, compiling my code with just this 'for' loop returns a fail when I upload it for checking (although it compiles no problem). Even if I don't use any of the 'for' loops output the mere fact it is there gives me this error.
Where am I going wrong?
Any help on this would be much appreciated.
Thanks!
First of all, keep in mind that execution speed is most often more valuable than memory use. If you first go look for spaces and after that allocate memory, you have to iterate through the array twice. This is an optimization of memory use at the cost of execution speed. So it might make more sense to just allocate a "large enough" array of lets say 100 characters and keep the code that you have.
I then want to make the array [spaces + 1] as if there are 2 spaces then there will be 3 initials
Keep in mind that C strings are null terminated, so you need to allocate room for the null terminator too, spaces + 1 + 1.
compiling my code with just this 'for' loop returns a fail when I upload it for checking (although it compiles no problem). Even if I don't use any of the 'for' loops output the mere fact it is there gives me this error.
What error? Does it compile or does it not compile, your text is contradicting.
Make sure you initialize spaces to zero.
As a side note, never use "magic numbers" in C code. if (name[i] == 32), 32 is gibberish to anyone who can't cite the ASCII table by memory. In addition, it is non-portable to systems with other symbol tables that might not have the same index numbers. Instead write:
if (name[i] == ' ')
In my opinion, a good approach to cater for such situations is the one the library function snprintf uses: It requires you to pass in the string to fill and the size of that string. In ensures that the string isn't overwritten and that the string is zero-terminated.
The function returns the length of the characters written to the string if the had the string been large enough. You can now do one of two things: Guess a reasonable buffer size and accept that the string will be cut short occasionally. Or call the function with a zero length, use the return value to allocate a char buffer and then fill it with a second call.
Applying this approach to your initials problem:
int initials(char *ini, int max, const char *str)
{
int prev = ' '; // pretend there's a space before the string
int n = 0; // actual number of initials
while (*str) {
if (prev == ' ' && *str != ' ') {
if (n + 1 < max) ini[n] = *str;
n++;
}
prev = *str++;
}
if (n < max) {
ini[n] = '\0';
} else if (max > 0) {
ini[max] = '\0';
}
return n;
}
You can then either use the fixed-size bufer approach:
char *name = "Theodore Quick Brown Fox";
char ini[4];
initials(ini, sizeof(ini), name);
puts(ini); // prints "TQB", "F" is truncated
Or the two-step dynamic-size approach:
char *name = "Theodore Quick Brown Fox";
int n;
n = initials(NULL, 0, name);
char ini[n + 1];
initials(ini, sizeof(ini), name);
puts(ini); // prints "TQBF"
(Note that this implementation of initals will ignore multiple spaces and spaces at the end or at the beginning of the string. Your look-one-ahead function will insert spaces in these cases.)
You know your initials array can't be any bigger than the name itself; at most, it can't be more than half as big (every other character is a space). So use that as your size. The easiest way to do that is to use a variable-length array:
size_t n = strlen( name ); // strlen returns a size_t type, not int
char initials[n/2+1]; // n/2+1 is not a *constant expression*, so this is
// a variable-length array.
memset( initials, 0, n + 1 ); // since initials is a VLA, we can't use an initializer
// in the declaration.
The only problem is that VLA support may be iffy - VLAs were introduced in C99, but made optional in C2011.
Alternately, you can use a dynamically-allocated buffer:
#include <stdlib.h>
...
size_t n = strlen( name );
char *initials = calloc( n/2+1, sizeof *initials ); // calloc initializes memory to 0
/**
* code to find and display initials
*/
free( initials ); // release memory before you exit your program.
Although, if all you have to do is display the initials, there's really no reason to store them - just print them as you find them.
Like others have suggested, use the character constant ' ' instead of the ASCII code 32 for comparing against a space:
if ( name[j] == ' ' )
or use the isspace library function (which will return true for spaces, tabs, newlines, etc.):
#include <ctype.h>
...
if ( isspace( name[j] ) )

getting input with fgets() in a loop

I am trying to take input with fgets(). I know how many lines I will get but it changes and I store the number of lines in the variable var. I also have another variable named part; it is the length of the line I get, but since there are white spaces between the values I multiplied it by 2 (I couldn't find another solution; I could use some advice).
Anyway, I tried to get the input as in the code below, but when I entered the first line it automatically breaks out the for loop and prints random things. I think it is to do with the fgets() in the loop; I don't know if there is a use of fgets() like this.
char inp[var][(2*part)];
int k,l;
for(k=0;k<=var;k++);
fgets(inp[k],(2*part),stdin);
printf("%c\n",inp[0]);
printf("%c\n",inp[1]);
printf("%c\n",inp[2]);
printf("%c\n",inp[3]);
…since there are white spaces between the values I multiplied it with 2…
If you aren't required to store everything on the stack, you can instead store the strings in dynamically allocated memory. For example:
char* inp[var];
char buf[400]; // just needs to be long
for (k = 0; k < var; k++) {
fgets(buf, 400, stdin);
inp[k] = malloc(sizeof(char) * (strlen(buf) + 1));
strcpy(inp[k], buf);
}
Although technically not standards-compliant, strdup is widely available and makes this easier as well.
As far as the actual issue, as BLUEPIXY said in the comments above, you have a few typos.
After the for loop, the semicolon makes it act unexpectedly.
for(k=0;k<=var;k++);
fgets(inp[k],(2*part),stdin);
is actually the same as
for(k=0;k<=var;k++) {
; // do nothing
}
fgets(...);
Remove that semicolon after the for loop statement. As it is, you're not actually reading correctly, which is why you see garbage.
To print an entire string, the printf family needs a %s format flag.
With your bounds on k, there will actually be var + 1 iterations of the loop. If var were 3, then k = 0,1,2,3 -> terminate when k checked at 4.
Typically, the safest and easiest way to use fgets is to allocate a single, large-enough line buffer. Use that to read the line, then copy it into correctly sized buffers.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void) {
// Allocate just the space for the list, not the strings themselves.
int num_input = 5;
char *input[num_input];
// Allocate our reusable line buffer.
char line[1024];
for( int i = 0; i < num_input; i++ ) {
// Read into the line buffer.
fgets(line, 1024,stdin);
// Copy from the line buffer into correctly sized memory.
input[i] = strdup(line);
}
for( int i = 0; i < num_input; i++ ) {
printf("%s\n",input[i]);
}
}
Note that strdup() is not an ISO C function, but POSIX. It's common and standard enough. It's too handy not to use. Write your own if necessary.
That takes care of not knowing the line length.
If you don't know the number of lines you're storing, you'll have to grow the array. Typically this is done with realloc to reallocate the existing memory. Start with a small list size, then grow it as needed. Doubling is a good rough approximation that's a pretty efficient balance between speed (reallocating can be slow) and memory efficiency.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
int main(void) {
// How big the input list is.
size_t input_max = 64;
// How many elements are in it.
size_t input_size = 0;
// Allocate initial memory for the input list.
// Again, not for the strings, just for the list.
char **input = malloc( sizeof(char*) * input_max );
char line[1024];
while( fgets(line, 1024,stdin) != NULL ) {
// Check if we need to make the input list bigger.
if( input_size >= input_max ) {
// Double the max length.
input_max *= 2;
// Reallocate.
// Note: this is only safe because we're
// going to exit on error, otherwise we'd leak
// input's memory.
input = realloc( input, sizeof(char*) * input_max );
// Check for error.
if( input == NULL ) {
fprintf(stderr, "Could not reallocate input list to %zu: %s", input_max, strerror(errno) );
exit(1);
}
}
input[input_size] = strdup(line);
input_size++;
}
for( size_t i = 0; i < input_size; i++ ) {
printf("%s\n",input[i]);
}
}
As you can see, this gets a bit complicated. Now you need to keep track of the array, its maximum size, and its current size. Anyone using the array must remember to check its size and grow it, and remember to error check it. Your next impulse will be to create a struct to collect all that together, and functions to manage the list.
This is a good exercise in dynamic memory management, and I encourage you to do it. But for production code, use a pre-existing library. GLib is a good choice. It contains all sorts of handy data structures and functions that are missing from C, including pointer arrays that automatically grow. Use them, or something like it, in production code.

Displaying lines with given number of characters

I have written such a program which suppose to returns lines which are containing at least 11 characters and 4 digits. I messed up something with types of variables I guess but I cant figure out how should I fix it.
#include <stdio.h>
#include <ctype.h>
#include <string.h>
int main()
{
char line[200];
char *temp[200];
int i = 0, k=0;
printf("Enter a string: \n");
while(fgets(line, sizeof(line),stdin))
{
int numberAlpha = 0;
int numberDigit = 0;
int i;
for(i=0; i<strlen(line); i++){
if(isalpha(line[i])) numberAlpha++;
else if(isdigit(line[i])) numberDigit++;
}
if(numberAlpha+numberDigit>10 && numberDigit>3){
temp[i]=line;
i++;
}
}
while(temp[k]!='\0'){
printf("%s", temp[k]);
k++;
}
return 0;
}
You're reusing the same buffer each time, and you're storing a pointer to that buffer in your temp array. What you're going to end up with is a bunch of the same pointer in that array, with that pointer pointing at the last line in the file.
What you can do instead is to rewrite your temp[i]=line statement to the following:
temp[i] = malloc(sizeof(line))
memcpy(temp[i], line, sizeof(line))
In so doing, you'll be creating a new array with the contents of the matching line, which won't get overwritten when you come around and read the next line out of the file.
Note that, because you're allocating that on the heap, at the end of your function you'll want to free it:
while (temp[k] != '\0') {
printf(...);
free(temp[k]);
k++
}
As said before , one issue is with copying of
temp[i]=line;
This can be solved by doing a new heap allocation and doing memcopy to temp.
The other issue that i could see is - with the value of variable i. Then temp array will always be assigned to strlen(line) index. You might be thinking of storing in the temp array from 0. Which is not happening.
This can be solved by-
int start_index=0;
while(...){
if(numberAlpha+numberDigit>10 && numberDigit>3){
temp[start_index]=line;
start_index++;
}
}
The problem is you are assigning the same address here:
temp[i]=line;
and line is used in the loop to read as well. That means it's overwritten in every iteration.
Instead, you can use strdup() (POSIX function):
temp[i] = strdup(line);
to copy the lines you are interested in. If strdup() not available you can use malloc() + strcpy() to do the same. Plus, free() them later.
In addition, be aware that:
fgets() will read in the newline character if there's room in the buffer which may not be what you want. So, you need to trim it out. You can do it with:
line[strcspn(line, "\n")] = 0; /* trim the trailing newline, if any */
The arguments to isalpha() and isdigit() should be cast to unsigned char to avoid potential undefined behaviour i.e. these two lines:
if(isalpha(line[i])) numberAlpha++;
else if(isdigit(line[i])) numberDigit++;
should be
if(isalpha((unsigned char)line[i])) numberAlpha++;
else if((unsigned char)isdigit(line[i])) numberDigit++;

Reversing a string on C

I'm new in in code and I'm doing K&R for C coding, but I have some simple questions that are complicating me, I know it can be a very stupid question but again, I'm new and if you can explain me in a way that a noob would understand I will appreciate it.
Just want to store "4321" in srev[] but it just doesn't print anything, I know there is other ways to reverse a string but I would like to know why this one doesn't work, thanks.
#include <stdio.h>
#define MAXL 1000
char s[MAXL] = "1234";
char srev[MAXL];
main(){
int i =0;
for(i=0; 4>=i; ++i){
srev[i] = s[4-i];
}
printf("srev[]: %s", srev);
}
To expand upon the comment by Dunno: the string "1234"in C is five bytes long. The fifth byte s[4] is a zero byte denoting string termination.
Your code copies that zero byte to srev[0], so now you have a C string that terminates before it has even begun.
Use i<4 in your for loop (and adjust the arithmetic to 3-i accordingly) so that you only swap the non-zero bytes. Then set srev[4] = '\0'; explicitly to terminate the new string in the correct place.
In your for loop the last thing you do is put s[4] into srev[0]. The that element (the fifth because arrays are zero indexed) is the strings null terminator. That means that the first thing in srev tells printf to stop printing.
Change your loop to this:
for(i=0; 3>=i; ++i){
srev[i] = s[3-i];
}
or:
for(i=0; 4 > i; ++i){
srev[i] = s[3-i];
}
becuase s[4] = '\0' which means end of character string. if you assign null terminator to a string it means you tell it: "it's the end, accept no more characters":
#include <stdio.h>
#define MAXL 1000
char s[MAXL] = "1234";
char srev[MAXL];
main(){
int i = 0;
for(i=0; 4 > i; ++i){
srev[i] = s[3-i]; // 3 - 0 = 3 so s[3] = '4' s4 = '\0'
}
printf("srev[]: %s", srev);
printf("\n\n");
}

memcmp with arrays of arrays

In C, I want to check a given array of chars for an arbitrary letter, and change it according to what it is. For example, the characters "a" or "A" would be changed to "4"(the character representing 4). This is a coding excercise for me :)
The code is as follows:
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <zlib.h>
#define NUM_BUFFERS 8
#define BUFFER_LENGTH 1024
char buffArrays[NUM_BUFFERS][BUFFER_LENGTH];
int main(int argc, const char* arg[v])
{
const char a[] = "a";
gzFile file;
file = gzopen("a.txt", "rb"); //contains 8 lines of 1024 'a's
int counter = 0;
while(counter < NUM_BUFFERS)
{
gzread(file, buffArrays[counter], BUFFER_LENGTH - 1);
counter++;
}
counter = 0;
while(counter < NUM_BUFFERS)
{
int i = 0;
for( i; i < BUFFER_LENGTH; i++ )
{
int *changed = &buffArrays[counter][i];
if( memcmp(&a, changed, 1) == 0 )
printf("SUCCESS\n");
}
counter++;
}
gzclose(file);
return 0;
}
This code never reaches the "SUCCESS" part. This says to me that either
(1) the value of changed is not pointing to the correct thing
(2) the pointer &a is incorrect
(3) I am completely wrong and it is something else
Any help would be appreciated.
Two things.
The following assigns the value 0x61 or 'a' to the character string.
const char a[] = 'a';
You probably rather meant to write
const char a = 'a'; /* assign a character to a character */
or
const char a[] = "a"; /* assign a string to a string */
The next thing is with the following statement. Hereby you assign a pointer to an int with the memory address of a char. Which invokes undefined behavior as you are reading over the bounds of your valid memory in the next statement.
int *changed = &bufferArrays[counter][i];
Hereby you compare the first four bytes starting from both addresses. Both variables are only one byte wide.
if( memcmp(&a, changed, 4) == 0 )
If you only want to know whether there is an 'a' in some of your buffer, why don't you just.
int i, j;
for (i = 0; i < NUM_BUFFERS; i++)
for (j = 0; j < BUFFER_LENGTH; j++)
if (bufferArrays[i][j] == 'a') printf("got it!\n");
This:
bufferArrays[counter] = "a"; //all the buffers contain one "a"
is wrong, since bufferArrays[counter] is not a character pointer but a character array. You need:
strcpy(bufferArrays[counter], "a");
Also, you don't show readTOmodify, so that part is a bit hard to understand.
Further, strings are best compared with strcpy(), which compares character-by-character and stops at the terminating '\0'. You use memcmp(), and I don't understand the reason for the 4 which is the number of bytes you're comparing.
1) bufferArrays[counter] = "a"; //all the buffers contain one "a"
This is not ok, you have to use strcpy to copy strings:
strcpy(bufferArrays[counter],"a"); //all the buffers contain one "a"
2)
#define BUFFER_LENGTH 1
Here's a problem. Buffer length should be at least 2 if you want to store just one char (for the extra null-termination).
3) In both of your loops, you never change counter, which leads to infinite loop.
Where's your code? I don't see any function surrounding it.
EDIT:
To assign you can also use:
while(counter < NUM_BUFFERS)
{
bufferArrays[counter][0] = 'a'; //all the buffers contain one "a"
counter++;
}
In any case, you have to have Buffer length as 2 if you want use it as a C-string.
The statement
bufferArrays[counter] = "a";
is not legal. It assigns a pointer to a single char and should give a compiler error (or at least a warning). Instead try
bufferArrays[counter] = 'a';
Also, in the while loops (both of them) you do not increase counter and so loop over the same index over and over forever.
Edit: Further problems
The condition where you do the comparison is flawed as well:
memcmp(&a, changed, 4)
The above doesn't compare pointers, it compares the contents of what the pointers point to, and you compare four bytes while the contents is only a single byte. Besides, you can't compare the pointers, as they will be different; The contents of the variable a is stored at a different location than that of the contents of bufferArrays[counter][i].

Resources