473,748 Members | 10,649 Online
Bytes | Software Development & Data Engineering Community
+ Post

Home Posts Topics Members FAQ

multiple realloc w/ malloc blows away data

WL
Hey, all. I'm creating an array of strings (char **argv style) on
the fly, and using realloc to create string pointers, and malloc
for the strings itself (if that makes any sense).

I'm using the construct
ptr = realloc(ptr, size);
*ptr = malloc(string_l ength);
strncpy(ptr, src, string_length);
to call realloc() multiple times. This should be ok, right?

Anyways, the problem I'm seeing is in (no error checking)

char **template, **temp_ptr;

/* initial template malloc */

template = realloc(templat e, 2 * sizeof **template);
temp_ptr = template + 1;
*temp_ptr = malloc(5);
strncpy(*temp_p tr, "abcd", 5);
printf("%s\n%s\ n%d %d\n", *template, *(template+1), template, temp_ptr);

template = realloc(templat e, 3 * sizeof **template);
temp_ptr = template + 2;
*temp_ptr = malloc(5);
strncpy(*temp_p tr, "wxyz", 5);
printf("%s\n%s\ n%s\n%d %d\n", *template, *(template+1), *(template+2), template, temp_ptr);

and the associated output is

1234
abcd
536952928 536952936
@
abcd
wxyz
536952928 536952944

After some futzing, it seems the data in *template gets blown away
after the last malloc(). I've looked this over all morning, but I
don't see where I'm going wrong.

The compiler is gcc 2.95.3 on alpha/netbsd 1.6.1.

WL
--
real mail: wliao at sdf loSnPesAtarM org
(remove the uppercase letters...)
Nov 13 '05 #1
9 4029
In article <be************ @ID-182745.news.uni-berlin.de>, WL wrote:
Hey, all. I'm creating an array of strings (char **argv style) on
the fly, and using realloc to create string pointers, and malloc
for the strings itself (if that makes any sense).

I'm using the construct
ptr = realloc(ptr, size);
*ptr = malloc(string_l ength);
strncpy(ptr, src, string_length);
to call realloc() multiple times. This should be ok, right?

Anyways, the problem I'm seeing is in (no error checking)

char **template, **temp_ptr;

/* initial template malloc */

template = realloc(templat e, 2 * sizeof **template);

[-]
Which means template = realloc( template, 2 * sizeof (char) ) and
since you didn't set template to NULL realloc() doesn't do the initial
allocation for you but presumes template, which may be 0xdeadbeef or
anything else here, to be a valid address.

So it'd read ...
char** template = NULL;

template = realloc( template, 2 * sizeof (*template) );

.... to start with.
[-]
Ta',
Juergen

--
\ Real name : Juergen Heinzl \ no flames /
\ EMail Private : ju*****@mananna n.org \ send money instead /
Nov 13 '05 #2
WL wrote:
Hey, all. I'm creating an array of strings (char **argv style) on
the fly, and using realloc to create string pointers, and malloc
for the strings itself (if that makes any sense).

I'm using the construct
ptr = realloc(ptr, size);
*ptr = malloc(string_l ength);
strncpy(ptr, src, string_length);
to call realloc() multiple times. This should be ok, right?

Anyways, the problem I'm seeing is in (no error checking)

char **template, **temp_ptr;

/* initial template malloc */

template = realloc(templat e, 2 * sizeof **template); Ouch ouch ouch.
There are two problems with this line.
1) The alloc'ed size is plain wrong. Think. template is declared as a
char **, right ? Thus **template has type char. Hence sizeof **template
is exactly 1. I don't think that's what you want.

2) That said, remember that realloc, just like malloc, can fail. If it
fails, it returns a NULL pointer. Thus, with your statement above you
are actually erasing the only reference you had to some alloc'ed data.
You thus can't free it anymore. I don't think that's what you want either.

The correct way to do what you want is:

temp_ptr = realloc(templat e, 2 * sizeof *template);
if (NULL == temp_ptr)
{
/* do some cleanup, possibly:
free(template);
although that's not likely */
}
else
{
template = temp_ptr;
/* carry on with processing */
} temp_ptr = template + 1;
*temp_ptr = malloc(5);
strncpy(*temp_p tr, "abcd", 5);
printf("%s\n%s\ n%d %d\n", *template, *(template+1), template, temp_ptr);

template = realloc(templat e, 3 * sizeof **template); Keep in mind *alloc functions are likely to be expensive. Use them
sparingly. Instead of increasing the size of your array of string one
string pointer at a time, start with a fair size (to be determined
depending on your application's requirements), keep track of the size,
and when you run out of room, *double* the size ! temp_ptr = template + 2;
*temp_ptr = malloc(5);
strncpy(*temp_p tr, "wxyz", 5);
printf("%s\n%s\ n%s\n%d %d\n", *template, *(template+1), *(template+2), template, temp_ptr);


--
Bertrand Mollinier Toublet
"Reality exists" - Richard Heathfield, 1 July 2003

Nov 13 '05 #3
WL wrote:

Hey, all. I'm creating an array of strings (char **argv style) on
the fly, and using realloc to create string pointers, and malloc
for the strings itself (if that makes any sense).

I'm using the construct
ptr = realloc(ptr, size);
*ptr = malloc(string_l ength);
strncpy(ptr, src, string_length);
to call realloc() multiple times. This should be ok, right?
No, not at all right. In the first line there's a problem
if realloc() fails and returns NULL. Guess what? You've stored
the NULL value into `ptr', and you can no longer find the memory
`ptr' used to point to. This is called a "potential memory leak."

Then in the second line, you always store the new pointer
in the very first slot of the reallocated array, clobbering
whatever lived there before. That is, the second time you do
this you lose track of the string you stored the first time,
and the third time you lose the string stored the second time,
and so on. This is called a "big-time memory leak."

The second and/or third lines may also be in error in the
use of `string_length' , whose initialization you haven't shown.
If `string_length' is the strlen() of some string, then the
copied string winds up lacking a terminating '\0' character --
that is, it's no longer a "string" as defined by C, and you
dare not attempt to use it as such.

Finally, there's no error-checking. "Omitted for brevity,"
I'm sure, but considering the rate at which errors are being
committed in this code, brevity here seems to be the soul of
something other than wit.
Anyways, the problem I'm seeing is in (no error checking)

char **template, **temp_ptr;

/* initial template malloc */

template = realloc(templat e, 2 * sizeof **template);
First, `template' has never been initialized to point to
anything in particular. If it's at file scope it's implicitly
initialized to NULL, and that's all right. But if it's local
to a function, it "points to garbage" and you are in for big
trouble.

Second, see above for a reason to avoid `p = realloc(p,...)' .

Third, that size calculation is wrong. R-O-N-G, wrong.
Hint: the value is two. Not "two pointers' worth," but two.
You want something like `2 * sizeof *template' instead.

Fourth, "anyways" is not an English word.
temp_ptr = template + 1;
*temp_ptr = malloc(5);
Since you only allocated two bytes' worth of memory above
(if you even got that far, given that `template' was uninitialized
when you called realloc() on it), it's quite likely that `temp_ptr'
now points outside the allocated area. When you try to store the
result of malloc() there, who knows what you might be stepping
upon?
strncpy(*temp_p tr, "abcd", 5);
Not an error, exactly, but this habit you've fallen into of
using strncpy() instead of strcpy() is not a good one. The habit
of using magic numbers like `5' and `2' is perhaps even worse.
printf("%s\n%s\ n%d %d\n", *template, *(template+1), template, temp_ptr);
You seem unconcerned about the lake of brimstone that awaits
persistent sinners, because here comes another one. Another
three, actually.

First, what's stored in `*template'? Well, you never stored
anything there, did you? So what reason have you to believe it
points to the start of a valid string that you can use with the
"%s" specifier? None at all, that's right ...

Second and third, what makes you think you can print a pointer
value with the "%d" specifier? "%d" is for integers, not for
pointers. If it happens to do something halfway sensible on your
particular system, that's pure dumb luck.
template = realloc(templat e, 3 * sizeof **template);
Same errors in the previous realloc(). You're up to a whole
three bytes now -- and note that if realloc() decided to copy the
old stuff to a new location, it has not obligingly copied the
things you stored outside the bounds of the old area ...
temp_ptr = template + 2;
*temp_ptr = malloc(5);
strncpy(*temp_p tr, "wxyz", 5);
printf("%s\n%s\ n%s\n%d %d\n", *template, *(template+1), *(template+2), template, temp_ptr);
All the same errors, all over again.
and the associated output is
Considering all the problems, it's a bit surprising that
you got any output at all.
1234
abcd
536952928 536952936
@
abcd
wxyz
536952928 536952944

After some futzing, it seems the data in *template gets blown away
after the last malloc(). I've looked this over all morning, but I
don't see where I'm going wrong.


If you can't see what's wrong with what you've written, you
are in serious need of a C course. Pointers are obviously a
mystery to you, and memory management seems to be a dark art.
Read Sections 4 through 7 -- ALL of it! -- in the comp.lang.c
Frequently Asked Questions (FAQ) list

http://www.eskimo.com/~scs/C-faq/faq.html

.... and it would do you no lasting harm to read Section 8 as
well. Until you improve your understanding, you are just
wasting your time writing stuff that looks like code but lacks
sensible content.

--
Er*********@sun .com
Nov 13 '05 #4


WL wrote:
Hey, all. I'm creating an array of strings (char **argv style) on
the fly, and using realloc to create string pointers, and malloc
for the strings itself (if that makes any sense).

I'm using the construct
ptr = realloc(ptr, size);
*ptr = malloc(string_l ength);
strncpy(ptr, src, string_length);
to call realloc() multiple times. This should be ok, right?

Anyways, the problem I'm seeing is in (no error checking)

char **template, **temp_ptr;

/* initial template malloc */

template = realloc(templat e, 2 * sizeof **template);
temp_ptr = template + 1;
*temp_ptr = malloc(5);
strncpy(*temp_p tr, "abcd", 5);
printf("%s\n%s\ n%d %d\n", *template, *(template+1), template, temp_ptr);

template = realloc(templat e, 3 * sizeof **template);
temp_ptr = template + 2;
*temp_ptr = malloc(5);
strncpy(*temp_p tr, "wxyz", 5);
printf("%s\n%s\ n%s\n%d %d\n", *template, *(template+1), *(template+2), template, temp_ptr);

and the associated output is

1234
abcd
536952928 536952936
@
abcd
wxyz
536952928 536952944

After some futzing, it seems the data in *template gets blown away
after the last malloc(). I've looked this over all morning, but I
don't see where I'm going wrong.

The compiler is gcc 2.95.3 on alpha/netbsd 1.6.1.


Nothing is wrong with the compiler. You have the allocations
and printfs in the code all messed up with undefined behavior.
Here is an example of how you may want to proceed.

#include <stdlib.h>
#include <string.h>
#include <stdio.h>

int main(void)
{
char **template, **temp_ptr;
unsigned i, count = 0;

template = malloc((count+1 )*sizeof(*templ ate));
if(template == NULL) /* Exit the program */
exit(EXIT_FAILU RE);
if((template[count] = malloc(5)) == NULL)
{ /* Deallocate and exit */
free(template);
exit(EXIT_FAILU RE);
}
strcpy(template[count],"abcd");
count++;
temp_ptr = realloc(templat e,(count+1)*siz eof(*template)) ;
if(temp_ptr == NULL)
{ /* Free the allocations and Exit */
for(i = 0; i < count;i++) free(template[i]);
free(template);
exit(EXIT_FAILU RE);
}
template = temp_ptr;
if((template[count] = malloc(5)) == NULL)
{
for(i = 0; i < count;i++) free(template[i]);
free(template);
exit(EXIT_FAILU RE);
}
strcpy(template[count],"wxyz");
count++;
for(i = 0;i < count;i++)
printf("templat e[%u] = \"%s\"\n",i,tem plate[i]);

/* Free the allocations */
for(i = 0; i < count;i++) free(template[i]);
free(template);
return 0;
}

--
Al Bowers
Tampa, Fl USA
mailto: xa*@abowers.com base.com (remove the x)
http://www.geocities.com/abowers822/

Nov 13 '05 #5
Eric Sosman <Er*********@su n.com> wrote:
WL wrote:

Hey, all. I'm creating an array of strings (char **argv style) on
the fly, and using realloc to create string pointers, and malloc
for the strings itself (if that makes any sense).
strncpy(*temp_p tr, "abcd", 5);


Not an error, exactly, but this habit you've fallen into of
using strncpy() instead of strcpy() is not a good one. The habit
of using magic numbers like `5' and `2' is perhaps even worse.


Why is using strncpy instead of strcpy bad? Doesn't declaring the
length keep the destination buffer safer compared to strcpy?

Thanks for all the comments (in the other followups as well). After
more reading and trying, here is what I ended up with, and it works
on 2 specific compiler/platform combination I'm testing it on.

Critique away :)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int ac, char **av)
{
char **template = NULL, **temp_ptr = NULL, **ptr = NULL;

template = malloc(sizeof *template);
if (template == NULL) { exit(1); }
*template = malloc(5);

if (*template == NULL) { exit(1); }
strncpy(*templa te, "1234", 5);

ptr = realloc(templat e, 2 * sizeof *template);
if (ptr == NULL) { exit(1); }
template = ptr;
temp_ptr = ptr + 1;
*temp_ptr = malloc(5);
if (*temp_ptr == NULL) { exit(1); }
strncpy(*temp_p tr, "abcd", 5);

ptr = realloc(templat e, 3 * sizeof *template);
if (ptr == NULL) { exit(1); }
template = ptr;
temp_ptr = ptr + 2;
*temp_ptr = malloc(5);
if (*temp_ptr == NULL) { exit(1); }
strncpy(*temp_p tr, "wxyz", 5);

printf("%s\n%s\ n%s\n", *template, *(template+1), *(template+2));

return 0;
}

WL
--
real mail: wliao at sdf loSnPesAtarM org
(remove the uppercase letters...)
Nov 13 '05 #6


Eric Sosman wrote:
Until you improve your understanding, you are just
wasting your time writing stuff that looks like code but lacks
sensible content.

Oooo, dang that's good. If I had a .sig I'd snag that, but I don't so I
won't.


Brian Rodenborn
Nov 13 '05 #7
In article <be************ @ID-182745.news.uni-berlin.de>,
Wenchi <wl***@otaku.fr eeshell.org> wrote:
Eric Sosman <Er*********@su n.com> wrote:
WL wrote:

Hey, all. I'm creating an array of strings (char **argv style) on
the fly, and using realloc to create string pointers, and malloc
for the strings itself (if that makes any sense).

strncpy(*temp_p tr, "abcd", 5);


Not an error, exactly, but this habit you've fallen into of
using strncpy() instead of strcpy() is not a good one. The habit
of using magic numbers like `5' and `2' is perhaps even worse.


Why is using strncpy instead of strcpy bad? Doesn't declaring the
length keep the destination buffer safer compared to strcpy?


Read _exactly_ what strncpy does. Take note of two things: What happens
if the destination buffer is 10 Megabytes and you copy a five character
string? (Ouch, that hurts. ) What happens if the destination buffer is
five bytes and you copy a five character string? (Time bomb ticking. )
Nov 13 '05 #8

"Wenchi" <wl***@otaku.fr eeshell.org> wrote
Thanks for all the comments (in the other followups as well). After
more reading and trying, here is what I ended up with, and it works
on 2 specific compiler/platform combination I'm testing it on.

Critique away :)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int ac, char **av)


Style comment:
main is typically 'int main(int argc, char **argv)'
while ac and av are certainly not misleading, argc and argv are clearer.

<remainder snipped>
Nov 13 '05 #9


Wenchi wrote:

Thanks for all the comments (in the other followups as well). After
more reading and trying, here is what I ended up with, and it works
on 2 specific compiler/platform combination I'm testing it on.

Critique away :)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int ac, char **av)
{
char **template = NULL, **temp_ptr = NULL, **ptr = NULL;

template = malloc(sizeof *template);
if (template == NULL) { exit(1); }
*template = malloc(5);

if (*template == NULL) { exit(1); }
strncpy(*templa te, "1234", 5);

ptr = realloc(templat e, 2 * sizeof *template);
if (ptr == NULL) { exit(1); }
template = ptr;
temp_ptr = ptr + 1;
*temp_ptr = malloc(5);
if (*temp_ptr == NULL) { exit(1); }
strncpy(*temp_p tr, "abcd", 5);

ptr = realloc(templat e, 3 * sizeof *template);
if (ptr == NULL) { exit(1); }
template = ptr;
temp_ptr = ptr + 2;
*temp_ptr = malloc(5);
if (*temp_ptr == NULL) { exit(1); }
strncpy(*temp_p tr, "wxyz", 5);

printf("%s\n%s\ n%s\n", *template, *(template+1), *(template+2));

return 0;


It is not much of a critique rather a mention of my preference.
I prefer to use array indexing instead of pointer offsets in my
code. To me it makes the code easily understood it you code with
indexing.

However, your code is valid C, will compile and run as you expect.

At this point, you might consider expanding your understanding of
C by using some of the very good features of the language. I am
referring to encapsulating of data in structs and the use of
functions to do specific tasks. Your program of dynamically
allocating an array of strings would be a good example of using
these features of the language. You can make a struct that has
as members a pointer to the array and a counter to keep accurate
the number of elements(string s) in the array. Then you can write
functions to manage the struct.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct ARRAY
{
char **name;
unsigned count;
} ARRAY;

char *addARRAY(ARRAY *p, const char *name);
void freeARRAY(ARRAY *p);
void printARRAY(cons t ARRAY *p);

int main(void)
{
ARRAY presidents = {NULL, 0U}; /* initialize to begin */

addARRAY(&presi dents,"George Washington");
addARRAY(&presi dents,"Abe Lincoln");
addARRAY(&presi dents,"Richard Nixon");
puts("\tTHE LISTED PRESIDENTS\n");
printARRAY(&pre sidents);
freeARRAY(&pres idents);
return 0;
}

char *addARRAY(ARRAY *p, const char *name)
{
char **tmp;
unsigned i = p->count;

if((tmp = realloc(p->name,(i + 1U)*sizeof(*p->name))) == NULL)
return NULL;
p->name = tmp;
if((p->name[i] = malloc(strlen(n ame)+1U)) == NULL)
return NULL;
strcpy(p->name[i],name);
p->count++;
return p->name[i];
}

void freeARRAY(ARRAY *p)
{
unsigned i;

for(i = 0U;i < p->count;i++)
free(p->name[i]);
free(p->name);
p->name = NULL;
p->count = 0U;
}

void printARRAY(cons t ARRAY *p)
{
unsigned i;

for(i = 0U; i < p->count;i++)
puts(p->name[i]);
return;
}

--
Al Bowers
Tampa, Fl USA
mailto: xa*@abowers.com base.com (remove the x)
http://www.geocities.com/abowers822/

Nov 13 '05 #10

This thread has been closed and replies have been disabled. Please start a new discussion.

Similar topics

11
3557
by: Eitan Michaelson | last post by:
Hi, Can any one tell me what's wrong with this code? It leaks (acceding to bound checker), when it attempts to reallocate memory. The code is not pure C, but with minor adjustments any C compiler would compile this.
5
381
by: D² | last post by:
Hello, I was trying to enlarge the size of an array wqith this code: float data ; data = realloc (data, n*sizeof(float)); but I encountered an error during compile fase, is this sintax right? How can should I do?
86
4144
by: Walter Roberson | last post by:
If realloc() finds it necessary to move the memory block, then does it free() the previously allocated block? The C89 standard has some reference to undefined behaviour if one realloc()'s memory that was freed by realloc(), but the only way explicitly mentioned in the C89 standard to free memory via realloc() is to realloc() it down to 0 bytes. I had always assumed it would automatically free the previous memory, but is the behaviour...
18
2362
by: ifmusic | last post by:
I used to think that i knew to program in C but this problem is making me thing otherwise. i'm trying to do something trivial, i suppose. i have this struct: typedef struct{ int socket; char ip; }peers;
7
3777
by: Mischa | last post by:
Hello, I am trying to use realloc multiple times to extend an array of doubles but unfortunatly it keeps failing. I think I am mixing up the size to which the old memory block needs to be extended. So if it's already 128 in size it needs to be realloc'd to (ORIGSIZE + NEWSIZE) right ? I have provided a small example below, maybe that will make my describtion somewhat clearer... Thank you for any suggestions..
19
5749
by: ivan.leben | last post by:
Let's say I have a piece of allocated memory which I want to expand and reuse if possible or allocate in a different part of RAM if resizing is not possible, however, in the latter case I don't care about the old data and I don't need to copy it to the new location. Is there a standard function that could do that? As far as I understand the description of the realloc() function, it _always_ copies the data to the new location, even if that...
64
8374
by: Robert Seacord | last post by:
The C standard doesn't say anything about what happens when you call realloc with a size argument of 0. Both glibc and openbsd appear to return a valid pointer to a zero-sized object.. e.g. the return of a malloc(0). Does anyone know of a runtime where realloc() free'ed the object and then returned NULL? If so, it would make the following idiom for realloc() exploitable. Here's the idiom, snagged from an openbsd man page: if ((p2 =...
37
3201
by: ravi.cs.2001 | last post by:
Hi all, I m relatively new to C. I have few queries related to malloc(): #1. When we perform malloc(), the memory allocated dynamically comes from the heap area of the process in concern. Well, we then say that the heap has shrinked. my query is: Is it that the heap physically does not shrink but the perticular nodes are marked 'ALLOCATED' and for subsequent calls to malloc() the memory manager remembers them and does not reference...
29
7880
by: marvinla | last post by:
Hello! I'm a beginner in C, and I'm having trouble with a pointer-to-pointer reallocation. This piece of code works well, but Valkyrie warns some parts (pointed below), and is breaking my real code. #include <stdio.h> #include <stdlib.h>
0
8991
marktang
by: marktang | last post by:
ONU (Optical Network Unit) is one of the key components for providing high-speed Internet services. Its primary function is to act as an endpoint device located at the user's premises. However, people are often confused as to whether an ONU can Work As a Router. In this blog post, we’ll explore What is ONU, What Is Router, ONU & Router’s main usage, and What is the difference between ONU and Router. Let’s take a closer look ! Part I. Meaning of...
0
9541
Oralloy
by: Oralloy | last post by:
Hello folks, I am unable to find appropriate documentation on the type promotion of bit-fields when using the generalised comparison operator "<=>". The problem is that using the GNU compilers, it seems that the internal comparison operator "<=>" tries to promote arguments from unsigned to signed. This is as boiled down as I can make it. Here is my compilation command: g++-12 -std=c++20 -Wnarrowing bit_field.cpp Here is the code in...
1
9321
by: Hystou | last post by:
Overview: Windows 11 and 10 have less user interface control over operating system update behaviour than previous versions of Windows. In Windows 11 and 10, there is no way to turn off the Windows Update option using the Control Panel or Settings app; it automatically checks for updates and installs any it finds, whether you like it or not. For most users, this new feature is actually very convenient. If you want to control the update process,...
0
9247
tracyyun
by: tracyyun | last post by:
Dear forum friends, With the development of smart home technology, a variety of wireless communication protocols have appeared on the market, such as Zigbee, Z-Wave, Wi-Fi, Bluetooth, etc. Each protocol has its own unique characteristics and advantages, but as a user who is planning to build a smart home system, I am a bit confused by the choice of these technologies. I'm particularly interested in Zigbee because I've heard it does some...
0
8242
agi2029
by: agi2029 | last post by:
Let's talk about the concept of autonomous AI software engineers and no-code agents. These AIs are designed to manage the entire lifecycle of a software development project—planning, coding, testing, and deployment—without human intervention. Imagine an AI that can take a project description, break it down, write the code, debug it, and then launch it, all on its own.... Now, this would greatly impact the work of software developers. The idea...
1
6796
isladogs
by: isladogs | last post by:
The next Access Europe User Group meeting will be on Wednesday 1 May 2024 starting at 18:00 UK time (6PM UTC+1) and finishing by 19:30 (7.30PM). In this session, we are pleased to welcome a new presenter, Adolph Dupré who will be discussing some powerful techniques for using class modules. He will explain when you may want to use classes instead of User Defined Types (UDT). For example, to manage the data in unbound forms. Adolph will...
1
3312
by: 6302768590 | last post by:
Hai team i want code for transfer the data from one system to another through IP address by using C# our system has to for every 5mins then we have to update the data what the data is updated we have to send another system
2
2782
muto222
by: muto222 | last post by:
How can i add a mobile payment intergratation into php mysql website.
3
2215
bsmnconsultancy
by: bsmnconsultancy | last post by:
In today's digital era, a well-designed website is crucial for businesses looking to succeed. Whether you're a small business owner or a large corporation in Toronto, having a strong online presence can significantly impact your brand's success. BSMN Consultancy, a leader in Website Development in Toronto offers valuable insights into creating effective websites that not only look great but also perform exceptionally well. In this comprehensive...

By using Bytes.com and it's services, you agree to our Privacy Policy and Terms of Use.

To disable or enable advertisements and analytics tracking please visit the manage ads & tracking page.