I am writing a c program for my school assignment that prints my name and registration number in the thread. I am failing to properly typecast and get the required output in the thread function. Please help me. I need help with properly typecasting the 2D array that I am passing to the thread from the main. Basically, I want that when I type data[0] it gives me the first string that is my name and when I do data[1] it gives me the second string that is my registration number stored in the 2D array coming from the main
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<pthread.h>
void * fun(void* arr)
{
char* data=(char*) arr;
printf("Name: %s\n", data[0]);
printf("Registration number: %s\n", data[1]);
return 0;
}
int main()
{
pthread_t tid;
char data[2][20]={"sana", "005"};
pthread_create(&tid, NULL, &fun, data);
pthread_join(tid,NULL);
return 0;
}
CodePudding user response:
This is a bit tricky unless you make a typedef. I would do like this:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<pthread.h>
typedef char tStrings[2][20]; // Type of variable to pass to thread
void * fun(void* arr)
{
tStrings *data = arr; // We got a pointer to tStrings
printf("Name: %s\n", (*data)[0]);
printf("Registration number: %s\n", (*data)[1]);
return 0;
}
int main()
{
pthread_t tid;
tStrings data={"data1", "data2"};
pthread_create(&tid, NULL, &fun, &data); // Pass pointer to data (type tStrings)
pthread_join(tid,NULL);
return 0;
}
CodePudding user response:
Your program passes the address of the first char of a true 2-dimensional character array. Because the ascii code of 's' is 115, 'a' is 97 etc., the memory layout of the array is the byte sequence
115 97 110 97 0 ... (15 zero bytes) 48 48 53 ... (17 zero bytes) | data[0] | data[1] |
This is an array consisting of two elements, each of which in turn is a 20 char array.
Using the array name data
as a function argument causes it to be "adjusted" to an address to its first element, data[0]
, a 20 char array.
In the function you then cast it to the address of a character; it now points to the character with the value 115. Your program crashes because then you index that pointer, which means you obtain the value (here: 115) of the character at that index (here: 0), and pass that to printf
as an address. The address 115 is not valid in your program; accessing it makes it crash.
All modern compilers warn here. gcc with -Wall
says " format ‘%s’ expects argument of type ‘char *’, but argument 2 has type ‘int’ " (it is int
and not char
because integer arguments, including chars, are converted to int
when passed to variadic functions).
Always compile with the highest warning level and understand and eliminate all warnings.
One fix would be to recognize the address as what it is, which is the address of an array of 20 chars:
void * fun(void* arr)
{
char (*data)[20] = (char (*)[20]) arr;
printf("Name: %s\n", data[0]);
printf("Registration number: %s\n", data[1]);
return 0;
}
The data type of data
is a bit unusual: It is a pointer to an array. We must dereference it first (it is a pointer, after all), obtaining an array of 20 chars; only then we can index that array to obtain a char
. That order is what the parentheses around *data
in the declaration char (*data)[20]
express. The cast is even funnier: As always in C, it mimics the declaration, just without the variable. It is an interesting feature of the C declaration syntax that there is exactly one valid position for an identifier in it, so that even very complicated casts (which omit the identifier!) are unambiguous.
As we said, the array elements of data
are themselves arrays; data[0]
is an array of 20 chars. You can safely pass that to printf
as a string like any other character array. It will be adjusted to a pointer to its first char, which is what printf
's %s
conversion expects.
The next element, data[1]
, is a character array as well.
The downside is that all strings must have the same maximum length and that the length is set at compile time. You can make it more maintainable by using a define instead of the 20
literal, but it's still fairly inflexible. What I'd do instead is passing an array of pointers. The definition of main's mainData
(I renamed it for clarity) is deceivingly similar but the semantics are entirely different. First the program:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<pthread.h>
void * fun(void* arr)
{
const char** data=(const char**) arr;
printf("Name: %s\n", data[0]);
printf("Registration number: %s\n", data[1]);
return 0;
}
int main()
{
pthread_t tid;
const char *mainData[] = {"sana", "005"};
pthread_create(&tid, NULL, &fun, mainData);
pthread_join(tid,NULL);
return 0;
}
const char *mainData[]
declares an array of character pointers, because the indexing operator [] has a higher precedence than the dereferencing *: We index first, obtaining a pointer, which we then dereference to get a char. Interestingly, this array can also be initialized with two string literals in curly braces, but the semantics are different: The array now holds only two addresses. The characters in the literal are not copied, they stay where they are; the two pointers in the array now simply point to their respective first character:
Somewhere in the programs static memory is the byte sequence for "sana":
115 <----- mainData[0] (say, address 0x12345678)
97
110
97
0
Somewhere else in memory the one for "005":
48 <----- mainData[1] (say, address 0x87654321)
48
53
0
The memory at mainData
now looks something like this:
0x12 0x34 0x56 0x78 0x87 0x65 0x43 0x21
|--- mainData[0] ---|--- mainData[1] --|
These are two plain pointers in an array which can (and do) point to character arrays of arbitrary length. Passing the array mainData
to a function adjusts it to a pointer to its first element. The first element is already a pointer, so the argument passed is a pointer to that pointer. This is what the cast const char** data=(const char**) arr;
in fun
reflects. (I made the pointers point to const char because that is good practice when pointing to const char ;-).
We secretly know that there are two valid pointers in the memory pointed to by data
; therefore we can access data[1]
as well. Passing a pointer to the first character in a zero terminated character string is fine; the program works as expected.
Passing a pointer to an array of pointers also has the advantage that the number of strings passed in the array can be made flexible because we can indicate the last element with a null pointer:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<pthread.h>
/** The argument is a pointer to the first element in an
array of pointers. The last element of the array
must be a nullpointer.
*/
void *printCharPtrArray(void* arr)
{
const char** data=(const char**) arr;
// loop until the address in the pointer data[i] is null,
// which indicates the last element in the array.
for(int i=0; data[i] != 0; i)
{
printf("Data =: ->%s<-\n", i, data[i]);
}
return 0;
}
int main()
{
pthread_t tid;
const char *mainData[] = {"sana", "", "005", 0};
pthread_create(&tid, NULL, printCharPtrArray, mainData);
pthread_join(tid,NULL);
const char *longData[]
= {"1", "", "2", "iuooioiu", "4", "popoi",
"123456" 2, &"123456"[2], &2["123456"], // these are all the same.
0
};
pthread_create(&tid, NULL, printCharPtrArray, longData);
pthread_join(tid,NULL);
return 0;
}
For the fun of it I demonstrated that indexing is, against its outer appearance, a symmetrical operation (a[i]
translates to *(a i)
, and a i
is the same as i a
because addition is commutative): a[i]
is the same as i[a]
which looks even funnier when a
is a string literal.