How to output unsigned char to a text file in c? - c

I've got an array of unsigned chars that I'd like to output to a file using the C file I/O. What's a good way to do that? Preferably for now, I'd like to output just one unsigned char.
My array of unsigned chars is also non-zero terminated, since the data is coming in binary.

I'd suggest to use function fwrite to write binary data to a file; obviously the solution works for arrays of SIZE==1 as well:
int main() {
#define SIZE 10
unsigned char a[SIZE] = {1, 2, 3, 4, 5, 0, 1, 2, 3, 4 };
FILE *f1 = fopen("file.bin", "wb");
if (f1) {
size_t r1 = fwrite(a, sizeof a[0], SIZE, f1);
printf("wrote %zu elements out of %d requested\n", r1, SIZE);
fclose(f1);
}
}

If you have, as you claim, an array of input (not just a pointer to the first element of the array), you don't need a null terminator. The array will also have length information:
size_t len = sizeof myarray;
// To write a single char:
fputc(myarray[0], fp);
// To write the entire array:
fwrite(myarray, 1, len, fp);
If you do want to output the entire array

I've got an array of unsigned chars that I'd like to output
How to output unsigned char to a text file in c?
The tricky part is "to a text file". This usually means to save something human readable.
If that is the goal, then save printable characters as text and non-printable ones as escaped text - also special handling of '\n' and '\\'.
#include <ctype.h>
#include <stdio.h>
void print_1_uchar(FILE *stream, unsigned char uch) {
if ((isprint(uch)) && ch != '\\') || ch == '\n') {
fputc(uch, stream);
} else {
fprintf(stream, "\\x03o", uch);
}
}
void print_1_array(FILE *stream, const void *a, size_t sz) {
const unsigned char *s = a;
while (sz-- > 0) {
print_1_uchar(stream, *s++);
}
}
Usage
unsigned char foo[] = { 1,2,3,4,5 };
print_1_array(stdout, foo, sizeof foo);

Related

Reading a comma-seperated list of numbers fails C

I have a file containing a list of numbers separated by commas. I tried different methods of reading data, and this piece of code has worked without issues on different datasets.
Input for example (600 values): https://pastebin.com/AHJ5UpEu
#include <stdio.h>
#include <stdint.h>
#include <malloc.h>
#include <mem.h>
#define READ "r"
#define MAX_LINE_SIZE 4096
#define DATA_DELIMITER ","
unsigned char *readInput(const char *filename, size_t inputs) {
unsigned char *input = malloc(sizeof(unsigned char) * inputs);
unsigned char nbr;
const char *token;
int i;
FILE *inputPtr = fopen(filename, READ);
char line[MAX_LINE_SIZE];
while (fgets(line, MAX_LINE_SIZE, inputPtr)) {
nbr = 0;
for (token = strtok(line, DATA_DELIMITER); token && *token; token = strtok(NULL, ",\n")) {
input[nbr] = (unsigned char) atoi(token);
nbr++;
}
break;
}
fclose(inputPtr);
if(nbr != inputs){
printf("Error, did not read all files. Only read %d\n",nbr);
exit(-1);
}
exit(0);
}
int main() {
unsigned char *d = readInput("../traces/inputs.dat", 600);
free(d);
exit(0);
}
Though it only reads the first 88 values. If I change the max-line-size to for example 512, this number is 145.
Though the value should - if I understand this correct - be equal to the length of the line, in my case ~2100 characters. So using 4098 shouldn't be an issue.
Please do correct me if I'm wrong.
How come I'm not reading all 600 values, but only parts of the data?
nbr is being used like an integer counter but is defined as an unsigned char. A char is one byte, and an unsigned byte has a range of 0 to 255. Incrementing beyond 255 will cause the byte to overflow and return to a value of 0. So, currently, nbr is actually the total number of entries processed mod 256.

Using strstr to find four different partial words

I am looking to find part of a string containing TP2, DP3, OP1, or OP2, in a text file.
On each line is a different set of characters and eventually these three characters are used, but they are never on the same line as each other.
I can get it to print once I find the OP2, but it will not print the three before it. If I comment out the OP2 it finds OP1 and if I do that to OP1 and OP2 it finds DP3 and so on.
I do not get why it cannot print out all four different ones once found.
I used two different methods one where I strcpy into a temp and one I just print it as is and neither work. Later I want it to print to the right of the = sign on the lines with the four search types, but I will work on that after I get the print issue fixed. Any help or reasons why would be much appreciated.
#include < stdio.h>
#include < stdlib.h>
#include < string.h>
#define MAX_LINE_LENGTH 150
int main(void) {
FILE *file1, *file2;
char parts[MAX_LINE_LENGTH+1];
int len = strlen(parts);
//char TP2[3] = "TP2";
char DP3[3] = "DP3";
char MOP1[3] = "OP1";
//char MOP2[3] = "OP2";
//char TP2Temp[MAX_LINE_LENGTH];
char DP3Temp[MAX_LINE_LENGTH];
char MOP1Temp[MAX_LINE_LENGTH];
//char MOP2Temp[MAX_LINE_LENGTH];
file1 = fopen("input.txt", "r");
file2 = fopen("output2.txt", "w");
if (file1 == NULL || file2 ==NULL) {
exit(1);
}
while(fgets(parts, sizeof(parts), file1)!=NULL){
if(parts[len -1 ] =='\n'){
parts[len -1 ] ='\0';
}
//if(strstr(parts, TP2)!=NULL){
// strcpy(TP2Temp, parts);
// fprintf(file2, "%s", TP2Temp);
//}
if(strstr(parts,DP3)!=NULL){
strcpy(DP3Temp, strstr(parts,DP3));
fprintf(file2, "%s", DP3Temp);
}
else if(strstr(parts, MOP1)!=NULL){
strcpy(MOP1Temp, strstr(parts,MOP1));
fprintf(file2, "%s", MOP1Temp);
}
/*else if(strstr(parts, MOP2)!=NULL){
strcpy(MOP2Temp, parts);
fprintf(file2, "%s", MOP2Temp);
}*/
}
fclose(file1);
fclose(file2);
return 0;
}
/*Here is the text file sample
TC_TP1[2]=1
TC_TP2[2]="9070036"
TC_TP3[2]=1
TC_TP4[2]=1
TC_TP5[2]=1
TC_TP6[2]=1
TC_TP7[2]=1
TC_DP1[2,1]=120
TC_DP2[2,1]=0
TC_DP3[2,1]=179.85
TC_DP4[2,1]=0
TC_DP5[2,1]=0
TC_MOP1[2,1]=3
TC_MOP2[2,1]=28
TC_MOP3[2,1]=0
TC_MOP4[2,1]=0
TC_TP1[3]=1
TC_TP2[3]="9005270"
TC_TP3[3]=1*/
char parts[MAX_LINE_LENGTH+1];
int len = strlen(parts);
parts is uninitialised in this code, and thus isn't guaranteed to contain a string. Even if it were to, len would be initialised the length of that garbage string, which is meaningless and thus useless.
char DP3[3] = "DP3";
If your understanding of strings is valid, you should realise there are four characters in these strings. The following program demonstrates this:
#include <stdio.h>
int main(void) {
printf("sizeof \"DP3\": %zu\n", sizeof "DP3");
}
You are reading a book to learn C, right? Your book would explain to you among many other things so we wouldn't need to, strstr requires its operands be strings, and strings always contain a terminating '\0'. Where's your terminating '\0'? How is strstr expected to know the length of the string pointed to by DP3?
Because the length of your tokens are at most three bytes, you currently only need to read and store at most three bytes at a time to conduct your search (four including the terminal byte explained above; untested&incomplete example below); this requirement could change, should you decide to introduce longer (or dynamically sized) tokens, your cursor will need to be as wide as your longest token.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef unsigned char item[4];
int item_cmp(void const *x, void const *y) {
return memcmp(x, y, sizeof (item));
}
int main(void) {
item cursor = "",
haystack[] = { "TP1", "OP0", "OP1", "OP2", "TP0", "DP3", "OOO" };
size_t size = fread(cursor, sizeof cursor - 1, 1, stdin),
nelem = sizeof haystack / sizeof *haystack;
int c = 0, e = !size;
qsort(haystack, nelem, sizeof *haystack, item_cmp);
do {
if (bsearch(cursor, haystack, nelem, sizeof *haystack, item_cmp)) {
printf("match found for %s\n", cursor);
}
memmove(cursor, cursor + 1, sizeof cursor - 1);
if (!e) {
c = fgetc(stdin);
e = c < 0 && feof(stdin);
}
cursor[size] = e || c == '\n' ? '\0' : c;
size -= e;
} while (size);
exit(0);
}
Thank you again BLUEPIXY, with your information I was able to make the changes I needed and was able to extract the data where it found TP2 and then the value after the equal sign. I am sure there is a nicer way to code this, but my solution is below. I will add a change to be able to take in any file name and the reason for the
MOP1Equal[strlen(MOP1Equal) -1] ='\0';
was to make it in columns for a csv file to put in excel and the
fprintf(file2, "\t%s", MOP1Equal+1);
where I add 1 was to get rid of the = sign.
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define MAX_LINE_LENGTH 150
int main(void) {
FILE *file1, *file2;
char parts[MAX_LINE_LENGTH+1] = "startingvaluebeforechange";
char TP2[4] = "TP2";
char DP3[4] = "DP3";
char MOP1[4] = "OP1";
char MOP2[4] = "OP2";
char Equal[2] = "=";
char TP2Temp[MAX_LINE_LENGTH];
char TP2Equal[MAX_LINE_LENGTH];
char DP3Temp[MAX_LINE_LENGTH];
char DP3Equal[MAX_LINE_LENGTH];
char MOP1Temp[MAX_LINE_LENGTH];
char MOP1Equal[MAX_LINE_LENGTH];
char MOP2Temp[MAX_LINE_LENGTH];
char MOP2Equal[MAX_LINE_LENGTH];
file1 = fopen("input.txt", "r");
file2 = fopen("output.txt", "w");
if (file1 == NULL || file2 ==NULL) {
exit(1);
}
while(fgets(parts, sizeof(parts), file1)!=NULL){
int len = strlen(parts);
if(parts[len -1 ] =='\n'){
parts[len -1 ] ='\0';
}
if(strstr(parts, TP2)!=NULL){
strcpy(TP2Temp, strstr(parts,TP2));
strcpy(TP2Equal, strstr(TP2Temp,Equal));
TP2Equal[strlen(TP2Equal) -2] ='\0';
fprintf(file2, "%s", TP2Equal+2);
}
if(strstr(parts,DP3)!=NULL){
strcpy(DP3Temp, strstr(parts,DP3));
strcpy(DP3Equal, strstr(DP3Temp,Equal));
DP3Equal[strlen(DP3Equal) -1] ='\0';
fprintf(file2, "\t%s", DP3Equal+1);
}
if(strstr(parts, MOP1)!=NULL){
strcpy(MOP1Temp, strstr(parts,MOP1));
strcpy(MOP1Equal, strstr(MOP1Temp,Equal));
MOP1Equal[strlen(MOP1Equal) -1] ='\0';
fprintf(file2, "\t%s", MOP1Equal+1);
}
if(strstr(parts, MOP2)!=NULL){
strcpy(MOP2Temp, strstr(parts,MOP2));
strcpy(MOP2Equal, strstr(MOP2Temp,Equal));
fprintf(file2, "\t%s", MOP2Equal+1);
}
}
fclose(file1);
fclose(file2);
return 0;
}

C Concatenate string in while loop

I'm trying to concatenate part of a struct with hex values. I run over every byte in the loop and convert to hex, then I want to concatenate all the hex into one long string.
However, I only end up with one value at the end of the loop. For some reason the string isnt concatenating properly. Any idea what Im doing wrong?
typedef struct OPTIONS_STR
{
int max;
int printName;
} OPTIONS;
void set_default_options(OPTIONS *options)
{
options->max = -1;
options->printName = 0;
}
void do_file(FILE *in, FILE *out, OPTIONS *options)
{
char ch;
int loop = 0;
char buf[81];
buf[0] = '\0';
int sz1;
int sz2;
int sz3;
int seeker = offsetof(struct myStruct, contents.datas);
//find total length of file
fseek(in, 0L, SEEK_END);
sz1 = ftell(in);
//find length from beggining to struct beginning and minus that from total length
fseek(in, seeker, SEEK_SET);
sz2 = sz1 - ftell(in);
//set seek location at beginning of struct offset
fseek(in, seeker, SEEK_SET);
sz3 = sz2 + 1;
char buffer[sz3];
char msg[sz3];
buffer[0] = '\0';
while (loop < sz2)
{
if (loop == sz2)
{
break;
}
fread(&ch, 1, 1, in);
sprintf(msg, "%02X", (ch & 0x00FF));
strcpy(buffer, msg);
++loop;
}
printf("%s\n", buffer);
}
int main(int argc, const char * argv[]) {
OPTIONS options;
set_default_options(&options);
const char *current = "/myfile.txt";
FILE *f = fopen(current, "rb");
do_file(f, stdout, &options);
fclose(f);
};
Use strcat instead of strcpy. That should fix your problem.
For efficiency look into using a write pointer like char *p = buffer and advance the write position with something like p += sprintf(p, "%02X", (ch & 0x00FF))
Also your if(loop == sz2) break check is a useless duplicate of the while(loop < sz2) check. The while loop won't execute if loop is equal or bigger than sz2.
Also wondering why you use fread when you only want one character. fgetc or getc seems to be a better choice.
Also, no matter if you use fread or getc you need to check for the end of file. What if the file does not have sz2 bytes in it? Because all modern systems are multiprocess and multiuser, so someone might cut the file short after the call to ftell. You should never assume things because even if you just checked it, it can change. Making that assumption is what causes TOCTTOU (Time of Check To Time Of Use) bugs.
In do_file(), you are copying the hex value for a single byte in a while loop. Thus, you should go to the next byte of character array buffer with each iteration of the while loop i.e. buffer++ or strcpy(buffer[loop], msg);

How to use fgets() to avoid casting its second argument which is of type int?

The declaration of the fgets function look like this:
char *fgets(char *str, int n, FILE *stream);
This means that the second argument is expected to be an int.
Which is the proper way to avoid this casting in the following program?
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void) {
const char *buffer = "Michi";
size_t len = strlen(buffer) + 1;
char arr[len];
printf("Type your Input:> ");
if (fgets(arr, (int)len, stdin) == NULL) {
printf("Error, fgets\n");
exit(1);
} else {
printf("Arr = %s\n", arr);
}
}
Here I used (int)len which looks fine, but what happens if buffer stores a very long string?
Lets say:
const char *buffer = "Very long long ..."; /* where length is beyond the range of the `int` type */
I already declared length of the type size_t which is fine here, but if I'll pass it to fgets is not OK because of:
conversion to ‘int’ from ‘size_t {aka long unsigned int}’ may alter its value
and if I cast it to int won't fit because the size of the int is smaller than the size of length and some information will be lost.
Maybe I'm missing something here...
Any way how should I avoid this situation?
#include <stdio.h>
char *fgets(char * restrict s, int n, FILE * restrict stream);
With fgets(), the input buffer s[INT_MAX] and beyond cannot be used.
OP's size_t len code could avoid the int cast of len by converting to a string and converting back to an int, that is just wasteful. A cast is the right thing to do.
Rather than littering code with a (int), reduce/control its usage and wrap the cast in a limiting helper function.
int fgets_len(size_t len) {
return (len < INT_MAX) ? (int) len : INT_MAX;
}
size_t len = something_big;
char *arr = malloc(len);
...
if ( fgets(arr, fgets_len(len), stdin) == NULL){
printf("Error, Fgets\n");
exit(1);
}else{
printf("Arr = '%s'\n", arr);
}
If code truly needs to read long lines, consider ssize_t getline(char **lineptr, size_t *n, FILE *stream); as defined here. Note this is a non-standard C library function, yet its source code is readily available.
Concerning pedantic use of fgets(), there are at least 2 reasons for fgets() to return NULL: End-of-file and input error. Now consider the corner case using OP's code
size_t len = strlen("") + 1;
char arr[len];
if ( fgets(arr, (int)len, stdin) == NULL){
and pathological cases like
if ( fgets(arr, 0, stdin) == NULL){
if ( fgets(arr, -1, stdin) == NULL){
Both are discussed at Is fgets() returning NULL with a short bufffer compliant?
If you want to avoid the trouble of integer overflow, you can do value checking and act accordingly. Note that, since strlen() returns value of type size_t you have 2 options. Either declare a variable of type int and assign it the return value of strlen(), which will do an implicit conversion from type size_t to int, or cast as you did in you function invocation.
Here is a possible solution:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
int main(void)
{
const char *buffer = "Michi";
//size_t len = strlen(buffer) + 1;
size_t len = 9999999999;
if (len > INT_MAX) {
fprintf(stderr, "Error, length exceeded desired value.Aborting...\n");
return 1;
}
char arr[len];
printf("Type your Input:> ");
if (fgets(arr, (int)len, stdin) == NULL) {
printf("Error, Fgets\n");
exit(1);
}
else {
printf("Arr = %s\n", arr);
}
return 0;
}
I wouldn't bother to link the dimension of the argument to fgets() with the dimension of buffer at all.
Instead, I'd ensure that the buffer being used for reading has a length that can be represented using an int (say, does not exceed INT_MAX). If you want to be truly portable, ensure the buffer length does not exceed 32767 (the standard specifies that the minimum allowed value of INT_MAX is 32767).
Then make use of the fact that fgets() will read a line in parts, if the line length exceeds the buffer length.
For example, assuming that len exceeds the length of any line being read from stdin;
char arr[len] = {0};
char read_buffer[10]; /* I'm reasonably confident that 10 < 32767 */
while (fgets(read_buffer, 10, stdin) != NULL)
{
size_t read_length = strlen(read_buffer);
if (read_length > 0)
{
if (read_buffer[read_length-1] != `\n`)
{
strcat(arr, read_buffer);
}
else
{
strncat(arr, read_buffer, read_length-1);
printf("Arr = %s\n", arr);
arr[0] = '\0'; /* clear arr so next line may be read */
/* break here if want to stop reading after the first line */
}
}
Note that, if end of file is not immediately preceded by a '\n', then the above will discard the text after the last '\n'.
In the above, replacing fgets(read_buffer, 10, stdin) with fgets(read_buffer, sizeof read_buffer, stdin) is safe, since a size_t with value less than or equal to INT_MAX can always safely be converted to int. So, if you want to shut up a compiler from issuing warnings, you can safely cast i.e. fgets(read_buffer, (int)(sizeof read_buffer), stdin)
Draft says that
The result of converting an integer to a shorter signed integer, or
the result of converting an unsigned integer to a signed integer of
equal length, if the value cannot be represented
is implementation-defined. As result if you want safely convert value of unsigned type to signed - you must ensure that all possible source values can be represented with target, signed type.

Program that reads in a text file as input and produces an output file which has all the original lines in alphabetical order

The code below works perfectly for what we need, but we were told late in the week that we cannot use strcmp as we have not covered it yet. Have you guys any suggestion of what i could use instead. I got help with the original code as i am new to pointers.
Thanks
Ok this is my new code. It is not sorting the text though.
I'm new so go easy
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Function for reading unsorted data from a file
int read(const char *filename, char* arr[]);
// Function for sorting gathered data in array
void sort(char* arr[], int N);
int my_strcmp(const char *a, const char *b);
// Function for writing sorted data to file
int write(const char *filename, char* arr[], int N);
// Helper function for swapping two words
void swap(char** a, char** b);
// Helper function for dealocatiing memory used by array
void final(char* arr[], int N);
int main(int argc, char *argv[])
{
char filename[260];
char* arr[1000]; // Suppose there are no more than 1000 lines
int N; // Size of filled array
// Request for input file name
printf("Input file name : ");
scanf("%s",filename);
// Perform reading file
if (-1 == (N = read(filename,arr))) {
// If error occure while opening the file
printf("File can't be opened.\n");
system("PAUSE");
return 0;
}
// Sort the data
sort(arr,N);
printf("Data sorted successfully.\n");
// Request for output file name
printf("Output file name : ");
scanf("%s",filename);
// Output sorted data
if (-1 == (N = write(filename,arr,N))) {
// If error occure while opening the file
printf("File can't be opened.\n");
system("PAUSE");
return 0;
}
system("PAUSE");
// Deallocate used memory
final(arr,N);
return 0;
}
int read(const char *filename, char* arr[])
{
char buffer[512]; // Suppose that line can't be longer than 511 symbols
int i = 0, len;
FILE * infile; // Input file
// Open file for reading
infile = fopen(filename,"r");
// Check whether file was opened correctly
if (!infile)
// If not then return error code
return -1;
// Read data
while (!feof(infile)) {
// Read one line (word)
fscanf(infile,"%s",buffer);
// Find the length of this word
len = strlen(buffer);
// Allocate memory for the word...
arr[i] = (char*)malloc(len+1);
// And copy the word to array
strcpy(arr[i],buffer);
// Increase counter
++i;
}
// Close the file
fclose(infile);
// Return number of elements of created array
return i;
}
void sort(char* arr[], int N)
{
int i,cmp;
for (--N; N>0; --N)
for (i=0; i<N; ++i) {
// Compare two words
cmp = my_strcmp(arr[i],arr[i+1]);
// If the first one is greater than second one (in alphabetical meaning)...
if (cmp>0)
// ...then swap them
swap(&arr[i],&arr[i+1]);
}
}
int write(const char *filename, char* arr[], int N)
{
int i;
FILE * outfile; // Output file
// Open file for writing
outfile = fopen(filename,"w");
// Check whether file was opened correctly
if (!outfile)
// If not then return error code
return -1;
// Write data
for (i=0; i<N-1; ++i)
// After each word output sign '\n' which means "new line"
fprintf(outfile,"%s\n",arr[i]);
// But after the last word don't write "new line"
fprintf(outfile,"%s",arr[N-1]);
// Close the file
fclose(outfile);
// Return success code
return 0;
}
void swap(char** a, char** b)
{
char *temp = *a;
*a = *b;
*b = temp;
}
void final(char* arr[], int N)
{
int i;
for (i=0; i<N; ++i)
free(arr[i]);
}
int my_strcmp(const char *a, const char *b)
{
while(*a==*b)
{
if ( *a == '\0' || *b == '\0' )
break;
a++;
b++;
}
if( *a == '\0' && *b == '\0' )
return 0;
else
return -1;
}
As #Gopi and #iharob suggested, you should write a strcmp replacement. First, remember to add a declaration before main with the same signature as strcmp:
int my_strcmp(const char *a,const char *b);
Also remember to change the cmp = strcmp(arr[i],arr[i+1]); call in the sort function to the new name cmp = my_strcmp(arr[i], arr[i+1]);.
Then write your own strcmp version. It's a simple function and you have already described what it does. cppreference gives a complete description of the function:
Defined in header
int strcmp( const char *lhs, constchar *rhs );
Compares two null-terminated byte strings lexicographically.
The sign of the result is the sign of the difference between the
values of the first pair of characters (both interpreted as unsigned
char) that differ in the strings being compared.
The behavior is undefined if lhs or rhs are not pointers to
null-terminated strings.
Parameters
lhs, rhs - pointers to the null-terminated byte strings to compare.
Return value
Negative value if lhs appears before rhs in lexicographical order.
Zero if lhs and rhs compare equal.
Positive value if lhs appears after rhs in lexicographical order.
You a have already described a simple implementation: "compare the first character of each string and if they are equal, compare the next two". If they aren't equal, you return a negative value if the first comes before the second in a lexicographical order and a positive value if it is the opposite. You return 0 if the strings are all equal.
int my_strcmp(const char *a, const char *b)
{
/* Your code here */
}
Things to have in mind:
Strings in c always end with the '\0' character, so you use that to test the end of a string. You can iterate over the whole string either incrementing the pointer or indexing it like an array and incrementing the index.
In lexicographical ordering, if you have strings of different lengths, but that are equal up to the point that the shorter one ends, the shorter one comes before the longer one. Example: "fly" comes before "flying".
The cppreference link describes a different implementation that will use less comparisons than yours and will probably be more efficient, but yours will behave correctly too.
Edit: Discussing your newly added function implementation. I won't provide an optimal or beautiful implementation, I will just discuss your code with the intention of helping you to achieve something that works correctly starting from the code you posted.
Your code (with better indentation):
int my_strcmp(const char *a, const char *b)
{
while(*a==*b)
{
if ( *a == '\0' || *b == '\0' )
break;
a++;
b++;
}
if( *a == '\0' && *b == '\0' )
return 0;
else
return -1;
}
The while loop iterates over both strings while they're equal. You also break the loop if you have reached the end of one of the strings by testing for the '\0' character. That's all very good. Your problem is after the loop.
There are four possible states after the loop:
You have reached the end of both strings. Both *a and *b are '\0'. That means that all characters were equal and the strings are, therefore, equal.
*a is '\0', but *b is a valid character. That means that the first string is shorter than the second, but they were equal up to the point that the first ended. That means that the first comes before the second.
The opposite of the previous state. *b is '\0' and *a is a valid character. That means the second comes before the first.
Both *a and *b are valid characters, they are just different. Testing the characters will define which string comes first.
Remember that you should return a negative value if the first string comes before the second, 0 if they are equal or a positive value the second string comes before the first. You could just test all possible states naively and return the appropriate value. After you have a working implementation, you can think about how you could keep the same behaviour while "cutting corners" to make it more succinct if needed.

Resources